Skip to main content

xkb_type/
lib.rs

1//! Virtual keyboard typing via evdev/uinput with XKB layout-aware key mapping.
2//!
3//! This crate provides:
4//! - [`Keyboard`] — a virtual uinput device that injects keystrokes.
5//! - [`XkbKeymap`] — reverse char→keycode lookup from the active XKB layout.
6//! - [`ClipboardBackend`] — trait + auto-detected clipboard implementations.
7//!
8//! # Example (auto-detect layout and clipboard)
9//!
10//! ```no_run
11//! use xkb_type::{Keyboard, Key};
12//! use std::time::Duration;
13//!
14//! let mut kb = Keyboard::new(Duration::from_millis(2))?;
15//! kb.type_text("Hello — こんにちは — €100 — 😀")?;
16//! kb.backspace(5)?;
17//! kb.send_combo(&[Key::KEY_LEFTCTRL, Key::KEY_C])?;
18//! # Ok::<(), anyhow::Error>(())
19//! ```
20//!
21//! # Example (explicit layout)
22//!
23//! ```no_run
24//! use xkb_type::Keyboard;
25//! use std::time::Duration;
26//!
27//! let mut kb = Keyboard::with_layout("de", None, Duration::from_millis(5))?;
28//! kb.type_text("Schöne Grüße")?;
29//! # Ok::<(), anyhow::Error>(())
30//! ```
31
32pub mod clipboard;
33pub mod keyboard;
34pub mod keymap;
35#[cfg(feature = "wayland-vk")]
36pub mod wayland_vk;
37
38// ---------------------------------------------------------------------------
39// Core types
40// ---------------------------------------------------------------------------
41
42/// A single keypress with optional Shift and/or AltGr modifiers.
43#[derive(Debug, Clone, Copy)]
44pub struct KeyTap {
45    pub keycode: u16,
46    pub shift: bool,
47    pub altgr: bool,
48}
49
50/// Information needed to produce a character at the cursor.
51///
52/// For most characters this is a single [`KeyTap`]. For characters that
53/// XKB only exposes as a dead-key combination (e.g. `ã` = `dead_tilde + a`
54/// on `us:intl`, or `'` = `dead_acute + space`), a `follow` tap is
55/// recorded so the typer emits the dead-key keypress followed by the
56/// base-letter (or space) keypress in sequence.
57#[derive(Debug, Clone, Copy)]
58pub struct KeyMapping {
59    pub main: KeyTap,
60    pub follow: Option<KeyTap>,
61}
62
63// ---------------------------------------------------------------------------
64// KeyInjector trait
65// ---------------------------------------------------------------------------
66
67/// High-level text-injection abstraction.
68///
69/// This unifies the available keystroke backends behind a single trait so a
70/// caller can pick an implementation at runtime (e.g. the layout-independent
71/// Wayland [`wayland_vk::WaylandVkKeyboard`] when the compositor supports
72/// `zwp_virtual_keyboard_v1`, falling back to the uinput [`Keyboard`]
73/// otherwise) without changing the typing code.
74///
75/// The semantics mirror [`Keyboard`]'s inherent methods.
76pub trait KeyInjector: Send {
77    /// Type `text` by injecting the corresponding keystrokes.
78    fn type_text(&mut self, text: &str) -> anyhow::Result<()>;
79
80    /// Emit Backspace `count` times.
81    fn backspace(&mut self, count: usize) -> anyhow::Result<()>;
82
83    /// Press all keys in `keys`, then release them in reverse order.
84    fn send_combo(&mut self, keys: &[evdev::Key]) -> anyhow::Result<()>;
85
86    /// Set the inter-event delay used between injected key events.
87    fn set_key_delay(&mut self, delay: std::time::Duration);
88}
89
90impl KeyInjector for Keyboard {
91    fn type_text(&mut self, text: &str) -> anyhow::Result<()> {
92        Keyboard::type_text(self, text)
93    }
94
95    fn backspace(&mut self, count: usize) -> anyhow::Result<()> {
96        Keyboard::backspace(self, count)
97    }
98
99    fn send_combo(&mut self, keys: &[evdev::Key]) -> anyhow::Result<()> {
100        Keyboard::send_combo(self, keys)
101    }
102
103    fn set_key_delay(&mut self, delay: std::time::Duration) {
104        Keyboard::set_key_delay(self, delay)
105    }
106}
107
108// ---------------------------------------------------------------------------
109// ClipboardBackend trait
110// ---------------------------------------------------------------------------
111
112/// Trait for clipboard get/set operations.
113pub trait ClipboardBackend: Send + Sync {
114    /// Read the current clipboard text content.
115    fn get_text(&self) -> anyhow::Result<String>;
116
117    /// Set the clipboard to the given text.
118    fn set_text(&self, text: &str) -> anyhow::Result<()>;
119
120    /// Read the primary selection (highlighted text, no Ctrl+C needed).
121    fn get_primary_selection(&self) -> anyhow::Result<String>;
122}
123
124// ---------------------------------------------------------------------------
125// Re-exports
126// ---------------------------------------------------------------------------
127
128pub use clipboard::{default_clipboard, NoopClipboard, WaylandClipboard, X11Clipboard};
129pub use keyboard::Keyboard;
130pub use keymap::{KeyboardLayout, XkbKeymap};
131
132// Re-export evdev's `Key` enum (and the full `evdev` module) so callers
133// of [`Keyboard::send_combo`] don't need a direct `evdev` dependency.
134pub use evdev;
135pub use evdev::Key;
136
137/// Convenience: build a [`Keyboard`] from the detected layout with a
138/// sensible default key delay (5 ms).
139///
140/// Returns an error if the XKB keymap cannot be built (missing locale data)
141/// or if `/dev/uinput` is not writable.
142///
143/// Equivalent to `Keyboard::new(Duration::from_millis(5))`.
144pub fn keyboard_from_detected_layout() -> anyhow::Result<Keyboard> {
145    Keyboard::new(std::time::Duration::from_millis(5))
146}