titan2-touchpadd/src/keyboard.rs
Peter Cai e15517aaf9 Emit simulated keyboard events from the keyboard uinput
Android doesn't like a mouse that also acts as a keyboard
2025-11-16 19:15:03 -05:00

156 lines
5.8 KiB
Rust

use std::{
collections::{HashMap, HashSet, hash_map::Entry},
sync::{
Arc, Mutex,
mpsc::{self, TryRecvError},
},
thread,
time::Instant,
};
use evdev::{Device, EventSummary, EventType, InputEvent, uinput::VirtualDevice};
use eyre::eyre;
use tracing::{debug, error};
use crate::{constants::*, gesture::TouchGestureInhibitor};
pub(crate) struct KeyboardHandler {
keyboard_dev: Device,
keyboard_uinput_dev: Option<Arc<Mutex<VirtualDevice>>>,
key_tx: mpsc::Sender<(u16, bool)>,
/// For "lockable" (or sticky) keys, stores when each of them was last pressed.
last_lockable_key_presses: HashMap<u16, Instant>,
/// The set of all keys that are currently "locked" down
locked_keys: HashSet<u16>,
}
impl KeyboardHandler {
pub fn start(
keyboard_dev: Device,
keyboard_uinput_dev: Option<Arc<Mutex<VirtualDevice>>>,
) -> impl TouchGestureInhibitor {
let (key_tx, key_rx) = mpsc::channel();
let handler = KeyboardHandler {
keyboard_dev,
keyboard_uinput_dev,
key_tx,
last_lockable_key_presses: {
let mut h = HashMap::new();
for key in KEYBOARD_LOCKABLE_KEYS {
h.insert(key, Instant::now());
}
h
},
locked_keys: HashSet::new(),
};
let inhibitor = KeyboardTouchInhibitor {
key_rx,
keyboard_state: HashMap::new(),
};
thread::spawn(move || {
if let Err(e) = handler.run() {
error!("keyboard handler loop exitted abnormally: {e:?}");
}
});
return inhibitor;
}
fn run(mut self) -> eyre::Result<()> {
loop {
for ev in self.keyboard_dev.fetch_events()? {
if let EventSummary::Key(kev, code, value) = ev.destructure() {
// Tell the touch side to reject input events for a while
self.key_tx.send((code.code(), value == 1))?;
// If we have a keyboard uinput dev it means we have keyboard features enabled
// Currently it just means a couple keys can be double-clicked to get their states locked
if let Some(ref mut keyboard_uinput_dev) = self.keyboard_uinput_dev {
// Clear all locked keys if a conflicting key is pressed or released
if KEYBOARD_LOCKED_KEYS_CONFLICTS.contains(&code.code())
&& !self.locked_keys.is_empty()
{
for locked_key in self.locked_keys.drain() {
keyboard_uinput_dev
.lock()
.unwrap()
.emit(&[InputEvent::new(EventType::KEY.0, locked_key, 0)])
.ok();
}
}
// Now emit the event as-is
keyboard_uinput_dev.lock().unwrap().emit(&[kev.into()]).ok();
// If this key is part of the "lockable" set, check whether it has been pressed in quick succession
// If so, temporarily "lock" its state to pressed until the next press (that happens naturally)
if value == 0
&& let Entry::Occupied(mut last_press) =
self.last_lockable_key_presses.entry(code.code())
{
let now = Instant::now();
if now.duration_since(*last_press.get()) < KEYBOARD_DOUBLE_PRESS_TIMEOUT
{
// "Lock" the Fn key. The next press will natually cancel this.
debug!("Key {} locked!", code.code());
self.locked_keys.insert(code.code());
keyboard_uinput_dev
.lock()
.unwrap()
.emit(&[InputEvent::new(EventType::KEY.0, code.code(), 1)])
.ok();
} else {
*last_press.get_mut() = now;
// Also, we should not consider this key "locked" now
// (Note that the precondition of this whole branch is value == 0, so either it's the start
// of a locked state, or the key isn't locked at all)
self.locked_keys.remove(&code.code());
}
}
}
}
}
}
}
}
pub(crate) struct KeyboardTouchInhibitor {
/// State tracker of all keys on the keyboard, true = down
keyboard_state: HashMap<u16, bool>,
key_rx: mpsc::Receiver<(u16, bool)>,
}
impl KeyboardTouchInhibitor {
fn handle_key(&mut self, key: u16, state: bool) {
self.keyboard_state.insert(key, state);
}
fn should_inhibit(&self) -> bool {
self.keyboard_state.values().any(|v| *v)
}
}
impl TouchGestureInhibitor for KeyboardTouchInhibitor {
fn next_should_inhibit(&mut self) -> eyre::Result<bool> {
let (key, state) = self.key_rx.recv()?;
self.handle_key(key, state);
// Handle any additiona key events we can handle immediately
loop {
match self.key_rx.try_recv() {
Ok((key, state)) => self.handle_key(key, state),
Err(TryRecvError::Disconnected) => return Err(eyre!("disconnected, aborting")),
Err(TryRecvError::Empty) => break,
}
}
Ok(self.should_inhibit())
}
}