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, KeyTap};
const DEVICE_SETTLE_DELAY: Duration = Duration::from_millis(200);
pub struct UinputKeyboard {
device: evdev::uinput::VirtualDevice,
keymap: XkbKeymap,
clipboard: ClipboardOps,
key_delay: Duration,
}
impl UinputKeyboard {
pub fn new(
keymap: XkbKeymap,
clipboard: ClipboardOps,
key_delay: Duration,
) -> 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,
key_delay,
})
}
fn set_modifier(&mut self, modifier: Key, pressed: bool) -> anyhow::Result<()> {
let value = if pressed { 1 } else { 0 };
self.device
.emit(&[InputEvent::new(EventType::KEY, modifier.code(), value)])?;
thread::sleep(self.key_delay);
Ok(())
}
fn tap_key(&mut self, tap: &KeyTap) -> anyhow::Result<()> {
if tap.shift {
self.set_modifier(Key::KEY_LEFTSHIFT, true)?;
}
if tap.altgr {
self.set_modifier(Key::KEY_RIGHTALT, true)?;
}
self.device
.emit(&[InputEvent::new(EventType::KEY, tap.keycode, 1)])?;
thread::sleep(self.key_delay);
self.device
.emit(&[InputEvent::new(EventType::KEY, tap.keycode, 0)])?;
thread::sleep(self.key_delay);
if tap.altgr {
self.set_modifier(Key::KEY_RIGHTALT, false)?;
}
if tap.shift {
self.set_modifier(Key::KEY_LEFTSHIFT, false)?;
}
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(self.key_delay);
Ok(())
}
fn inject_ctrl_v(&mut self) -> anyhow::Result<()> {
self.set_modifier(Key::KEY_LEFTCTRL, true)?;
self.device
.emit(&[InputEvent::new(EventType::KEY, Key::KEY_V.code(), 1)])?;
thread::sleep(self.key_delay);
self.device
.emit(&[InputEvent::new(EventType::KEY, Key::KEY_V.code(), 0)])?;
thread::sleep(self.key_delay);
self.set_modifier(Key::KEY_LEFTCTRL, false)?;
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.main)?;
if let Some(follow) = mapping.follow.as_ref() {
self.tap_key(follow)?;
}
} 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(&KeyTap {
keycode: Key::KEY_BACKSPACE.code(),
shift: false,
altgr: 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(())
}
}