use std::thread;
use std::time::Duration;
use anyhow::Context;
use evdev::{AttributeSet, EventType, InputEvent, Key};
use tracing::{debug, warn};
use super::clipboard::ClipboardOps;
use super::keymap::XkbKeymap;
use super::{ClipboardHandler, KeyInjector};
const KEY_DELAY: Duration = Duration::from_millis(2);
const DEVICE_SETTLE_DELAY: Duration = Duration::from_millis(200);
pub struct UinputKeyboard {
device: evdev::uinput::VirtualDevice,
keymap: XkbKeymap,
clipboard: ClipboardOps,
}
impl UinputKeyboard {
pub fn new(keymap: XkbKeymap, clipboard: ClipboardOps) -> anyhow::Result<Self> {
let mut keys = AttributeSet::<Key>::new();
for code in 1..=247 {
keys.insert(Key::new(code));
}
let device = evdev::uinput::VirtualDeviceBuilder::new()
.context("failed to create VirtualDeviceBuilder")?
.name("whisrs virtual keyboard")
.with_keys(&keys)
.context("failed to register key events")?
.build()
.context("failed to build uinput virtual device — check /dev/uinput permissions")?;
thread::sleep(DEVICE_SETTLE_DELAY);
debug!("uinput virtual keyboard created");
Ok(Self {
device,
keymap,
clipboard,
})
}
fn tap_key(&mut self, keycode: u16, shift: bool) -> anyhow::Result<()> {
let key = Key::new(keycode);
if shift {
self.device.emit(&[InputEvent::new(
EventType::KEY,
Key::KEY_LEFTSHIFT.code(),
1,
)])?;
thread::sleep(KEY_DELAY);
}
self.device
.emit(&[InputEvent::new(EventType::KEY, key.code(), 1)])?;
thread::sleep(KEY_DELAY);
self.device
.emit(&[InputEvent::new(EventType::KEY, key.code(), 0)])?;
thread::sleep(KEY_DELAY);
if shift {
self.device.emit(&[InputEvent::new(
EventType::KEY,
Key::KEY_LEFTSHIFT.code(),
0,
)])?;
thread::sleep(KEY_DELAY);
}
Ok(())
}
fn release_all_modifiers(&mut self) -> anyhow::Result<()> {
let modifiers = [
Key::KEY_LEFTSHIFT,
Key::KEY_RIGHTSHIFT,
Key::KEY_LEFTCTRL,
Key::KEY_RIGHTCTRL,
Key::KEY_LEFTALT,
Key::KEY_RIGHTALT,
Key::KEY_LEFTMETA,
Key::KEY_RIGHTMETA,
];
for modifier in &modifiers {
self.device
.emit(&[InputEvent::new(EventType::KEY, modifier.code(), 0)])?;
}
thread::sleep(KEY_DELAY);
Ok(())
}
fn inject_ctrl_v(&mut self) -> anyhow::Result<()> {
self.device
.emit(&[InputEvent::new(EventType::KEY, Key::KEY_LEFTCTRL.code(), 1)])?;
thread::sleep(KEY_DELAY);
self.device
.emit(&[InputEvent::new(EventType::KEY, Key::KEY_V.code(), 1)])?;
thread::sleep(KEY_DELAY);
self.device
.emit(&[InputEvent::new(EventType::KEY, Key::KEY_V.code(), 0)])?;
thread::sleep(KEY_DELAY);
self.device
.emit(&[InputEvent::new(EventType::KEY, Key::KEY_LEFTCTRL.code(), 0)])?;
thread::sleep(KEY_DELAY);
Ok(())
}
}
impl KeyInjector for UinputKeyboard {
fn type_text(&mut self, text: &str) -> anyhow::Result<()> {
self.release_all_modifiers()?;
let mut paste_buf = String::new();
for ch in text.chars() {
if let Some(&mapping) = self.keymap.lookup(ch) {
if !paste_buf.is_empty() {
self.paste_text(&paste_buf)?;
paste_buf.clear();
}
self.tap_key(mapping.keycode, mapping.shift)?;
} else {
paste_buf.push(ch);
}
}
if !paste_buf.is_empty() {
self.paste_text(&paste_buf)?;
}
Ok(())
}
fn backspace(&mut self, count: u32) -> anyhow::Result<()> {
self.release_all_modifiers()?;
for _ in 0..count {
self.tap_key(Key::KEY_BACKSPACE.code(), false)?;
}
Ok(())
}
fn paste_text(&mut self, text: &str) -> anyhow::Result<()> {
let saved = self.clipboard.get_text().ok();
self.clipboard
.set_text(text)
.context("failed to set clipboard for paste")?;
thread::sleep(Duration::from_millis(10));
self.release_all_modifiers()?;
self.inject_ctrl_v()?;
thread::sleep(Duration::from_millis(50));
if let Some(previous) = saved {
if let Err(e) = self.clipboard.set_text(&previous) {
warn!("failed to restore clipboard: {e}");
}
}
Ok(())
}
}