pub mod clipboard;
pub mod keyboard;
pub mod keymap;
#[cfg(feature = "wayland-vk")]
pub mod wayland_vk;
#[cfg(feature = "xtest")]
mod xtest;
use std::ops::ControlFlow;
#[derive(Debug, Clone, Copy)]
pub struct KeyTap {
pub keycode: u16,
pub shift: bool,
pub altgr: bool,
}
#[must_use]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Typed {
All,
StoppedAt(usize),
}
pub(crate) fn run_gated<S>(
steps: impl IntoIterator<Item = (usize, S)>,
gate: &mut dyn FnMut(usize) -> ControlFlow<()>,
mut send: impl FnMut(S) -> anyhow::Result<()>,
) -> anyhow::Result<Typed> {
for (at, step) in steps {
if gate(at).is_break() {
return Ok(Typed::StoppedAt(at));
}
send(step)?;
}
Ok(Typed::All)
}
#[derive(Debug, Clone, Copy)]
pub struct KeyMapping {
pub main: KeyTap,
pub follow: Option<KeyTap>,
}
pub trait KeyInjector: Send {
fn type_text(
&mut self,
text: &str,
gate: &mut dyn FnMut(usize) -> ControlFlow<()>,
) -> anyhow::Result<Typed>;
fn backspace(&mut self, count: usize) -> anyhow::Result<()>;
fn send_combo(&mut self, keys: &[evdev::Key]) -> anyhow::Result<()>;
fn set_key_delay(&mut self, delay: std::time::Duration);
}
impl KeyInjector for Keyboard {
fn type_text(
&mut self,
text: &str,
gate: &mut dyn FnMut(usize) -> ControlFlow<()>,
) -> anyhow::Result<Typed> {
Keyboard::type_text_gated(self, text, gate)
}
fn backspace(&mut self, count: usize) -> anyhow::Result<()> {
Keyboard::backspace(self, count)
}
fn send_combo(&mut self, keys: &[evdev::Key]) -> anyhow::Result<()> {
Keyboard::send_combo(self, keys)
}
fn set_key_delay(&mut self, delay: std::time::Duration) {
Keyboard::set_key_delay(self, delay)
}
}
pub trait ClipboardBackend: Send + Sync {
fn get_text(&self) -> anyhow::Result<String>;
fn set_text(&self, text: &str) -> anyhow::Result<()>;
fn get_primary_selection(&self) -> anyhow::Result<String>;
}
pub use clipboard::{default_clipboard, NoopClipboard, WaylandClipboard, X11Clipboard};
pub use keyboard::Keyboard;
pub use keymap::{KeyboardLayout, XkbKeymap};
pub use evdev;
pub use evdev::Key;
pub fn keyboard_from_detected_layout() -> anyhow::Result<Keyboard> {
Keyboard::new(std::time::Duration::from_millis(5))
}
#[cfg(test)]
mod tests {
use super::*;
fn run(steps: &[usize], break_at: Option<usize>) -> (Typed, Vec<usize>, Vec<usize>) {
let mut asked = Vec::new();
let mut sent = Vec::new();
let mut gate = |at: usize| {
asked.push(at);
if Some(at) == break_at {
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
};
let typed = run_gated(steps.iter().map(|&at| (at, at)), &mut gate, |step| {
sent.push(step);
Ok(())
})
.unwrap();
(typed, asked, sent)
}
#[test]
fn run_gated_asks_before_every_step_and_sends_them_all() {
let (typed, asked, sent) = run(&[0, 1, 3, 7], None);
assert_eq!(typed, Typed::All);
assert_eq!(asked, vec![0, 1, 3, 7]);
assert_eq!(sent, vec![0, 1, 3, 7]);
}
#[test]
fn run_gated_stops_before_the_step_the_gate_breaks_on() {
let (typed, asked, sent) = run(&[0, 1, 3, 7], Some(3));
assert_eq!(typed, Typed::StoppedAt(3));
assert_eq!(asked, vec![0, 1, 3], "nothing is asked after the break");
assert_eq!(sent, vec![0, 1], "the step it broke on is not sent");
let (typed, _, sent) = run(&[0, 1], Some(0));
assert_eq!(typed, Typed::StoppedAt(0));
assert!(sent.is_empty());
}
#[test]
fn run_gated_with_no_steps_is_all_without_asking() {
let (typed, asked, sent) = run(&[], Some(0));
assert_eq!(typed, Typed::All);
assert!(asked.is_empty() && sent.is_empty());
}
#[test]
fn run_gated_ends_at_a_send_error() {
let mut asked = 0;
let mut gate = |_: usize| {
asked += 1;
ControlFlow::Continue(())
};
let result = run_gated([(0, ()), (1, ()), (2, ())], &mut gate, |()| {
anyhow::bail!("device gone")
});
assert!(result.is_err());
assert_eq!(asked, 1, "no step is asked after a failed send");
}
}