use std::cell::RefCell;
use std::collections::HashMap;
use thiserror::Error;
use x11rb::connection::Connection;
use x11rb::protocol::xproto;
use x11rb::protocol::xtest::ConnectionExt as _;
use x11rb::xcb_ffi::{ConnectError, ConnectionError, ReplyError, XCBConnection};
use xkbcommon::xkb::KeyDirection;
struct KeymapState {
mapping: xkbcommon::xkb::Keymap,
modifier_keycode: HashMap<u8, u32>,
}
pub struct InputSynth {
connection: XCBConnection,
screen: usize,
mapping: RefCell<KeymapState>,
xkb_context: xkbcommon::xkb::Context,
}
unsafe impl Send for InputSynth {}
#[derive(Debug, Error)]
pub enum Error {
#[error("{0}")]
Connect(#[from] ConnectError),
#[error("{0}")]
Connection(#[from] ConnectionError),
#[error("{0}")]
Reply(#[from] ReplyError),
}
extern "C" {
fn xkb_keymap_key_get_mods_for_level(
_: *mut xkbcommon::xkb::ffi::xkb_keymap,
_: xkbcommon::xkb::ffi::xkb_keycode_t,
_: xkbcommon::xkb::ffi::xkb_layout_index_t,
_: xkbcommon::xkb::ffi::xkb_level_index_t,
_: *mut xkbcommon::xkb::ffi::xkb_mod_mask_t,
_: usize,
) -> usize;
}
type Result<T, E = Error> = std::result::Result<T, E>;
mod xkb_extra {
use xkbcommon::xkb::{
x11::ffi::{xkb_x11_keymap_new_from_device, xkb_x11_state_new_from_device},
Context, Keymap, KeymapCompileFlags, State,
};
#[must_use]
pub(super) fn keymap_new_from_device(
context: &Context,
connection: &x11rb::xcb_ffi::XCBConnection,
device_id: i32,
flags: KeymapCompileFlags,
) -> Keymap {
unsafe {
Keymap::from_raw_ptr(xkb_x11_keymap_new_from_device(
context.get_raw_ptr(),
connection.get_raw_xcb_connection() as *mut _,
device_id,
flags,
))
}
}
#[must_use]
pub(super) fn state_new_from_device(
keymap: &Keymap,
connection: &x11rb::xcb_ffi::XCBConnection,
device_id: i32,
) -> State {
unsafe {
State::from_raw_ptr(xkb_x11_state_new_from_device(
keymap.get_raw_ptr(),
connection.get_raw_xcb_connection() as *mut _,
device_id,
))
}
}
}
impl InputSynth {
pub fn new() -> Result<Self> {
let (connection, screen) = XCBConnection::connect(None)?;
let (xkb_major, xkb_minor) = x11rb::protocol::xkb::X11_XML_VERSION;
x11rb::protocol::xkb::use_extension(&connection, xkb_major as _, xkb_minor as _)?
.reply()?;
let (xtest_major, xtest_minor) = x11rb::protocol::xtest::X11_XML_VERSION;
connection
.xtest_get_version(xtest_major as _, xtest_minor as _)?
.reply()?;
let context = xkbcommon::xkb::Context::new(0);
connection.flush()?;
Ok(Self {
mapping: RefCell::new(Self::get_keymap_state(&connection, &context)?),
connection,
screen,
xkb_context: context,
})
}
fn get_keymap_state(
conn: &XCBConnection,
ctx: &xkbcommon::xkb::Context,
) -> Result<KeymapState> {
conn.flush()?;
let devices = x11rb::protocol::xinput::list_input_devices(conn)?.reply()?;
let device = devices
.devices
.iter()
.find(|d| d.device_use == x11rb::protocol::xinput::DeviceUse::IS_X_KEYBOARD)
.unwrap();
let mapping = xkb_extra::keymap_new_from_device(ctx, conn, device.device_id as _, 0);
let mut state = xkb_extra::state_new_from_device(&mapping, conn, device.device_id as _);
let mut modifier_keycode = HashMap::new();
mapping.key_for_each(|map, k| {
state.update_mask(0, 0, 0, 0, 0, 0);
state.update_key(k, KeyDirection::Down);
for m in 0..map.num_mods() {
if state.mod_index_is_active(m, xkbcommon::xkb::STATE_MODS_DEPRESSED) {
modifier_keycode.insert(m as u8, k);
}
}
});
Ok(KeymapState {
mapping,
modifier_keycode,
})
}
fn handle_events(&self) -> Result<()> {
while let Some(event) = self.connection.poll_for_event()? {
use x11rb::protocol::Event;
if let Event::MappingNotify(_) = event {
self.mapping
.replace(Self::get_keymap_state(&self.connection, &self.xkb_context)?);
}
}
Ok(())
}
pub fn click(&self, x: i16, y: i16, button: u8, press: bool) -> Result<()> {
self.handle_events()?;
self.connection
.xtest_fake_input(
if press {
xproto::BUTTON_PRESS_EVENT
} else {
xproto::BUTTON_RELEASE_EVENT
},
button,
x11rb::CURRENT_TIME,
self.connection.setup().roots[self.screen].root,
x,
y,
x11rb::NONE as _,
)?
.check()?;
Ok(())
}
pub fn move_cursor(&self, x: i16, y: i16) -> Result<()> {
self.handle_events()?;
self.connection
.xtest_fake_input(
xproto::MOTION_NOTIFY_EVENT,
0,
x11rb::CURRENT_TIME,
self.connection.setup().roots[self.screen].root,
x,
y,
x11rb::NONE as _,
)?
.check()?;
Ok(())
}
pub(crate) fn find_key_sequence(&self, sym: u16) -> Option<(Vec<u32>, u32)> {
let mapping = self.mapping.borrow();
let mut ans = None;
mapping.mapping.key_for_each(|map, k| {
if ans.is_none() {
let nlevels = map.num_levels_for_key(k, 0);
for level in 0..nlevels {
let syms = map.key_get_syms_by_level(k, 0, level);
if syms.len() == 1 && syms[0] == sym.into() {
ans.replace((level, k));
}
}
}
});
let mut mods = Vec::new();
if let Some((level, keycode)) = ans {
let mut masks = [0; 4];
unsafe {
xkb_keymap_key_get_mods_for_level(
mapping.mapping.get_raw_ptr(),
keycode,
0,
level,
masks.as_mut_ptr(),
4,
)
};
'next_mask: for mask in masks.iter() {
for m in 0..mapping.mapping.num_mods() {
if (*mask & (1 << m)) != 0 && !mapping.modifier_keycode.contains_key(&(m as _))
{
continue 'next_mask;
}
}
for m in 0..mapping.mapping.num_mods() {
if (*mask & (1 << m)) != 0 {
mods.push(*mapping.modifier_keycode.get(&(m as _)).unwrap())
}
}
return Some((mods, keycode));
}
}
None
}
pub fn ascii_char(&self, ch: u8) -> Result<()> {
self.handle_events()?;
let mut keysym: u16 = ch as _;
if (8..=17).contains(&ch) {
keysym += 0xff00;
}
if let Some((mods, keycode)) = self.find_key_sequence(keysym) {
for &m in &mods {
self.connection.xtest_fake_input(
xproto::KEY_PRESS_EVENT,
m as _,
x11rb::CURRENT_TIME,
self.connection.setup().roots[self.screen].root,
0,
0,
x11rb::NONE as _,
)?;
}
self.connection.xtest_fake_input(
xproto::KEY_PRESS_EVENT,
keycode as _,
x11rb::CURRENT_TIME,
self.connection.setup().roots[self.screen].root,
0,
0,
x11rb::NONE as _,
)?;
self.connection.xtest_fake_input(
xproto::KEY_RELEASE_EVENT,
keycode as _,
x11rb::CURRENT_TIME,
self.connection.setup().roots[self.screen].root,
0,
0,
x11rb::NONE as _,
)?;
for &m in mods.iter().rev() {
self.connection.xtest_fake_input(
xproto::KEY_RELEASE_EVENT,
m as _,
x11rb::CURRENT_TIME,
self.connection.setup().roots[self.screen].root,
0,
0,
x11rb::NONE as _,
)?;
}
self.connection.flush()?;
}
Ok(())
}
}
#[cfg(test)]
mod test {
#[test]
fn test_find_key_sequence() {
let is = super::InputSynth::new().unwrap();
let (mods, keycode) = is.find_key_sequence(b'A' as _).unwrap();
println!("{mods:?} {keycode}");
}
}