mirror of
https://github.com/feschber/lan-mouse.git
synced 2026-04-03 12:01:28 +03:00
move refcounting of key presses to input-emulation (#169)
This commit is contained in:
committed by
GitHub
parent
68361b25d1
commit
8f7890c9be
@@ -1,31 +1,34 @@
|
||||
use async_trait::async_trait;
|
||||
use std::fmt::Display;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
fmt::Display,
|
||||
};
|
||||
|
||||
use input_event::Event;
|
||||
use input_event::{Event, KeyboardEvent};
|
||||
|
||||
pub use self::error::{EmulationCreationError, EmulationError, InputEmulationError};
|
||||
|
||||
#[cfg(windows)]
|
||||
pub mod windows;
|
||||
mod windows;
|
||||
|
||||
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||
pub mod x11;
|
||||
mod x11;
|
||||
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
pub mod wlroots;
|
||||
mod wlroots;
|
||||
|
||||
#[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))]
|
||||
pub mod xdg_desktop_portal;
|
||||
mod xdg_desktop_portal;
|
||||
|
||||
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||
pub mod libei;
|
||||
mod libei;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod macos;
|
||||
mod macos;
|
||||
|
||||
/// fallback input emulation (logs events)
|
||||
pub mod dummy;
|
||||
pub mod error;
|
||||
mod dummy;
|
||||
mod error;
|
||||
|
||||
pub type EmulationHandle = u64;
|
||||
|
||||
@@ -66,8 +69,164 @@ impl Display for Backend {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InputEmulation {
|
||||
emulation: Box<dyn Emulation>,
|
||||
handles: HashSet<EmulationHandle>,
|
||||
pressed_keys: HashMap<EmulationHandle, HashSet<u32>>,
|
||||
}
|
||||
|
||||
impl InputEmulation {
|
||||
async fn with_backend(backend: Backend) -> Result<InputEmulation, EmulationCreationError> {
|
||||
let emulation: Box<dyn Emulation> = match backend {
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
Backend::Wlroots => Box::new(wlroots::WlrootsEmulation::new()?),
|
||||
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||
Backend::Libei => Box::new(libei::LibeiEmulation::new().await?),
|
||||
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||
Backend::X11 => Box::new(x11::X11Emulation::new()?),
|
||||
#[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))]
|
||||
Backend::Xdp => Box::new(xdg_desktop_portal::DesktopPortalEmulation::new().await?),
|
||||
#[cfg(windows)]
|
||||
Backend::Windows => Box::new(windows::WindowsEmulation::new()?),
|
||||
#[cfg(target_os = "macos")]
|
||||
Backend::MacOs => Box::new(macos::MacOSEmulation::new()?),
|
||||
Backend::Dummy => Box::new(dummy::DummyEmulation::new()),
|
||||
};
|
||||
Ok(Self {
|
||||
emulation,
|
||||
handles: HashSet::new(),
|
||||
pressed_keys: HashMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn new(backend: Option<Backend>) -> Result<InputEmulation, EmulationCreationError> {
|
||||
if let Some(backend) = backend {
|
||||
let b = Self::with_backend(backend).await;
|
||||
if b.is_ok() {
|
||||
log::info!("using emulation backend: {backend}");
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
for backend in [
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
Backend::Wlroots,
|
||||
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||
Backend::Libei,
|
||||
#[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))]
|
||||
Backend::Xdp,
|
||||
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||
Backend::X11,
|
||||
#[cfg(windows)]
|
||||
Backend::Windows,
|
||||
#[cfg(target_os = "macos")]
|
||||
Backend::MacOs,
|
||||
Backend::Dummy,
|
||||
] {
|
||||
match Self::with_backend(backend).await {
|
||||
Ok(b) => {
|
||||
log::info!("using emulation backend: {backend}");
|
||||
return Ok(b);
|
||||
}
|
||||
Err(e) if e.cancelled_by_user() => return Err(e),
|
||||
Err(e) => log::warn!("{e}"),
|
||||
}
|
||||
}
|
||||
|
||||
Err(EmulationCreationError::NoAvailableBackend)
|
||||
}
|
||||
|
||||
pub async fn consume(
|
||||
&mut self,
|
||||
event: Event,
|
||||
handle: EmulationHandle,
|
||||
) -> Result<(), EmulationError> {
|
||||
match event {
|
||||
Event::Keyboard(KeyboardEvent::Key { key, state, .. }) => {
|
||||
// prevent double pressed / released keys
|
||||
if self.update_pressed_keys(handle, key, state) {
|
||||
self.emulation.consume(event, handle).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
_ => self.emulation.consume(event, handle).await,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn create(&mut self, handle: EmulationHandle) -> bool {
|
||||
if self.handles.insert(handle) {
|
||||
self.emulation.create(handle).await;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn destroy(&mut self, handle: EmulationHandle) {
|
||||
let _ = self.release_keys(handle).await;
|
||||
if self.handles.remove(&handle) {
|
||||
self.emulation.destroy(handle).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn terminate(&mut self) {
|
||||
for handle in self.handles.iter().cloned().collect::<Vec<_>>() {
|
||||
self.destroy(handle).await
|
||||
}
|
||||
self.emulation.terminate().await
|
||||
}
|
||||
|
||||
pub async fn release_keys(&mut self, handle: EmulationHandle) -> Result<(), EmulationError> {
|
||||
if let Some(keys) = self.pressed_keys.get_mut(&handle) {
|
||||
let keys = keys.drain().collect::<Vec<_>>();
|
||||
for key in keys {
|
||||
let event = Event::Keyboard(KeyboardEvent::Key {
|
||||
time: 0,
|
||||
key,
|
||||
state: 0,
|
||||
});
|
||||
self.emulation.consume(event, handle).await?;
|
||||
if let Ok(key) = input_event::scancode::Linux::try_from(key) {
|
||||
log::warn!("releasing stuck key: {key:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let event = Event::Keyboard(KeyboardEvent::Modifiers {
|
||||
mods_depressed: 0,
|
||||
mods_latched: 0,
|
||||
mods_locked: 0,
|
||||
group: 0,
|
||||
});
|
||||
self.emulation.consume(event, handle).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn has_pressed_keys(&self, handle: EmulationHandle) -> bool {
|
||||
self.pressed_keys
|
||||
.get(&handle)
|
||||
.is_some_and(|p| !p.is_empty())
|
||||
}
|
||||
|
||||
/// update the pressed_keys for the given handle
|
||||
/// returns whether the event should be processed
|
||||
fn update_pressed_keys(&mut self, handle: EmulationHandle, key: u32, state: u8) -> bool {
|
||||
let Some(pressed_keys) = self.pressed_keys.get_mut(&handle) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if state == 0 {
|
||||
// currently pressed => can release
|
||||
pressed_keys.remove(&key)
|
||||
} else {
|
||||
// currently not pressed => can press
|
||||
pressed_keys.insert(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait InputEmulation: Send {
|
||||
trait Emulation: Send {
|
||||
async fn consume(
|
||||
&mut self,
|
||||
event: Event,
|
||||
@@ -77,64 +236,3 @@ pub trait InputEmulation: Send {
|
||||
async fn destroy(&mut self, handle: EmulationHandle);
|
||||
async fn terminate(&mut self);
|
||||
}
|
||||
|
||||
pub async fn create_backend(
|
||||
backend: Backend,
|
||||
) -> Result<Box<dyn InputEmulation>, EmulationCreationError> {
|
||||
match backend {
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
Backend::Wlroots => Ok(Box::new(wlroots::WlrootsEmulation::new()?)),
|
||||
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||
Backend::Libei => Ok(Box::new(libei::LibeiEmulation::new().await?)),
|
||||
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||
Backend::X11 => Ok(Box::new(x11::X11Emulation::new()?)),
|
||||
#[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))]
|
||||
Backend::Xdp => Ok(Box::new(
|
||||
xdg_desktop_portal::DesktopPortalEmulation::new().await?,
|
||||
)),
|
||||
#[cfg(windows)]
|
||||
Backend::Windows => Ok(Box::new(windows::WindowsEmulation::new()?)),
|
||||
#[cfg(target_os = "macos")]
|
||||
Backend::MacOs => Ok(Box::new(macos::MacOSEmulation::new()?)),
|
||||
Backend::Dummy => Ok(Box::new(dummy::DummyEmulation::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn create(
|
||||
backend: Option<Backend>,
|
||||
) -> Result<Box<dyn InputEmulation>, EmulationCreationError> {
|
||||
if let Some(backend) = backend {
|
||||
let b = create_backend(backend).await;
|
||||
if b.is_ok() {
|
||||
log::info!("using emulation backend: {backend}");
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
for backend in [
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
Backend::Wlroots,
|
||||
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||
Backend::Libei,
|
||||
#[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))]
|
||||
Backend::Xdp,
|
||||
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||
Backend::X11,
|
||||
#[cfg(windows)]
|
||||
Backend::Windows,
|
||||
#[cfg(target_os = "macos")]
|
||||
Backend::MacOs,
|
||||
Backend::Dummy,
|
||||
] {
|
||||
match create_backend(backend).await {
|
||||
Ok(b) => {
|
||||
log::info!("using emulation backend: {backend}");
|
||||
return Ok(b);
|
||||
}
|
||||
Err(e) if e.cancelled_by_user() => return Err(e),
|
||||
Err(e) => log::warn!("{e}"),
|
||||
}
|
||||
}
|
||||
|
||||
Err(EmulationCreationError::NoAvailableBackend)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user