use std::collections::VecDeque;
use x11rb::connection::Connection;
use x11rb::protocol::xproto::*;
use x11rb::protocol::xtest;
use x11rb::rust_connection::RustConnection;
use crate::{Error, InputMethodEvent, InputMethodState, Result};
const XK_SHIFT_L: Keysym = 0xFFE1;
const XK_CONTROL_L: Keysym = 0xFFE3;
const XK_BACKSPACE: Keysym = 0xFF08;
const XK_DELETE: Keysym = 0xFFFF;
const XK_U: Keysym = 0x0075;
const XK_SPACE: Keysym = 0x0020;
pub struct InputMethod {
connection: RustConnection,
#[allow(dead_code)]
screen_num: usize,
root_window: Window,
serial: u32,
keyboard_mapping: Option<CachedKeyboardMapping>,
events: VecDeque<InputMethodEvent>,
state: InputMethodState,
focused_window: Option<Window>,
}
struct CachedKeyboardMapping {
keysyms: Vec<Keysym>,
keysyms_per_keycode: usize,
min_keycode: Keycode,
}
impl InputMethod {
pub fn new() -> Result<Self> {
let (connection, screen_num) =
RustConnection::connect(None).map_err(|e| Error::ConnectionFailed(e.to_string()))?;
let setup = connection.setup();
let screen = &setup.roots[screen_num];
let root_window = screen.root;
let xtest_ext = connection
.query_extension(b"XTEST")
.map_err(|e| Error::ConnectionFailed(e.to_string()))?
.reply()
.map_err(|e| Error::ConnectionFailed(e.to_string()))?;
if !xtest_ext.present {
return Err(Error::ProtocolNotSupported(
"XTest extension not available".to_string(),
));
}
let _version = xtest::get_version(&connection, 2, 2)
.map_err(|e| Error::ConnectionFailed(e.to_string()))?
.reply()
.map_err(|e| Error::ConnectionFailed(e.to_string()))?;
let event_mask = EventMask::FOCUS_CHANGE | EventMask::PROPERTY_CHANGE;
connection
.change_window_attributes(
root_window,
&ChangeWindowAttributesAux::new().event_mask(event_mask),
)
.map_err(|e| Error::ConnectionFailed(e.to_string()))?;
connection
.flush()
.map_err(|e| Error::ConnectionFailed(e.to_string()))?;
let mut im = Self {
connection,
screen_num,
root_window,
serial: 0,
keyboard_mapping: None,
events: VecDeque::new(),
state: InputMethodState::new(),
focused_window: None,
};
im.refresh_keyboard_mapping()?;
im.check_focus()?;
Ok(im)
}
fn refresh_keyboard_mapping(&mut self) -> Result<()> {
let min_keycode = self.connection.setup().min_keycode;
let max_keycode = self.connection.setup().max_keycode;
let keycode_count = max_keycode - min_keycode + 1;
let mapping = self
.connection
.get_keyboard_mapping(min_keycode, keycode_count)
.map_err(|e| Error::ConnectionFailed(e.to_string()))?
.reply()
.map_err(|e| Error::ConnectionFailed(e.to_string()))?;
self.keyboard_mapping = Some(CachedKeyboardMapping {
keysyms: mapping.keysyms,
keysyms_per_keycode: mapping.keysyms_per_keycode as usize,
min_keycode,
});
Ok(())
}
fn check_focus(&mut self) -> Result<()> {
let focus = self
.connection
.get_input_focus()
.map_err(|e| Error::ConnectionFailed(e.to_string()))?
.reply()
.map_err(|e| Error::ConnectionFailed(e.to_string()))?;
let new_focus = if focus.focus != 0 {
Some(focus.focus)
} else {
None
};
if new_focus != self.focused_window {
self.focused_window = new_focus;
if new_focus.is_some() && !self.state.active {
self.serial += 1;
self.state.active = true;
self.state.serial = self.serial;
self.events.push_back(InputMethodEvent::Activate {
serial: self.serial,
});
} else if new_focus.is_none() && self.state.active {
self.state.active = false;
self.events.push_back(InputMethodEvent::Deactivate);
}
}
Ok(())
}
pub fn next_event(&mut self) -> Option<InputMethodEvent> {
if let Some(event) = self.events.pop_front() {
return Some(event);
}
loop {
match self.connection.poll_for_event() {
Ok(Some(event)) => {
if let Some(ime_event) = self.process_x11_event(&event) {
return Some(ime_event);
}
}
Ok(None) => break, Err(e) => {
log_warn!("X11 event error: {}", e);
break;
}
}
}
self.events.pop_front()
}
fn process_x11_event(&mut self, event: &x11rb::protocol::Event) -> Option<InputMethodEvent> {
match event {
x11rb::protocol::Event::FocusIn(focus_in) => {
log_debug!("FocusIn event for window: {:?}", focus_in.event);
self.focused_window = Some(focus_in.event);
if !self.state.active {
self.serial += 1;
self.state.active = true;
self.state.serial = self.serial;
return Some(InputMethodEvent::Activate {
serial: self.serial,
});
}
}
x11rb::protocol::Event::FocusOut(focus_out) => {
log_debug!("FocusOut event for window: {:?}", focus_out.event);
if self.state.active {
self.state.active = false;
return Some(InputMethodEvent::Deactivate);
}
}
x11rb::protocol::Event::KeyPress(key_press) => {
log_debug!(
"KeyPress event: keycode={}, state={:?}",
key_press.detail,
key_press.state
);
}
x11rb::protocol::Event::PropertyNotify(prop_notify) => {
log_debug!(
"PropertyNotify: atom={}, state={:?}",
prop_notify.atom,
prop_notify.state
);
}
x11rb::protocol::Event::MappingNotify(_) => {
log_debug!("MappingNotify - refreshing keyboard mapping");
if let Err(e) = self.refresh_keyboard_mapping() {
log_warn!("Failed to refresh keyboard mapping: {}", e);
}
}
_ => {}
}
None
}
pub fn dispatch(&mut self) -> Result<()> {
match self.connection.wait_for_event() {
Ok(event) => {
if let Some(ime_event) = self.process_x11_event(&event) {
self.events.push_back(ime_event);
}
Ok(())
}
Err(e) => Err(Error::ConnectionFailed(e.to_string())),
}
}
pub fn is_active(&self) -> bool {
self.state.active
}
pub fn commit_string(&self, text: &str) -> Result<()> {
let mapping = self
.keyboard_mapping
.as_ref()
.ok_or_else(|| Error::ConnectionFailed("No keyboard mapping".to_string()))?;
for c in text.chars() {
let keysym = char_to_keysym(c);
if keysym == 0 {
log_warn!("No keysym for character: {:?}", c);
continue;
}
if let Some((keycode, needs_shift)) = Self::find_keycode_for_keysym_in_mapping(
keysym,
&mapping.keysyms,
mapping.keysyms_per_keycode,
mapping.min_keycode,
) {
self.send_key_with_mapping(keycode, needs_shift, mapping)?;
} else {
self.send_unicode_input_with_mapping(c, mapping)?;
}
}
self.connection
.flush()
.map_err(|e| Error::CommitFailed(e.to_string()))?;
Ok(())
}
fn find_keycode_for_keysym_in_mapping(
keysym: Keysym,
keysyms: &[Keysym],
keysyms_per_keycode: usize,
min_keycode: Keycode,
) -> Option<(Keycode, bool)> {
for (i, chunk) in keysyms.chunks(keysyms_per_keycode).enumerate() {
let keycode = min_keycode + i as u8;
if !chunk.is_empty() && chunk[0] == keysym {
return Some((keycode, false));
}
if chunk.len() > 1 && chunk[1] == keysym {
return Some((keycode, true));
}
}
None
}
fn send_key_with_mapping(
&self,
keycode: Keycode,
needs_shift: bool,
mapping: &CachedKeyboardMapping,
) -> Result<()> {
let shift_keycode = Self::find_keycode_for_keysym_in_mapping(
XK_SHIFT_L,
&mapping.keysyms,
mapping.keysyms_per_keycode,
mapping.min_keycode,
)
.map(|(kc, _)| kc)
.unwrap_or(50);
if needs_shift {
xtest::fake_input(
&self.connection,
KEY_PRESS_EVENT,
shift_keycode,
x11rb::CURRENT_TIME,
self.root_window,
0,
0,
0,
)
.map_err(|e| Error::CommitFailed(e.to_string()))?;
}
xtest::fake_input(
&self.connection,
KEY_PRESS_EVENT,
keycode,
x11rb::CURRENT_TIME,
self.root_window,
0,
0,
0,
)
.map_err(|e| Error::CommitFailed(e.to_string()))?;
xtest::fake_input(
&self.connection,
KEY_RELEASE_EVENT,
keycode,
x11rb::CURRENT_TIME,
self.root_window,
0,
0,
0,
)
.map_err(|e| Error::CommitFailed(e.to_string()))?;
if needs_shift {
xtest::fake_input(
&self.connection,
KEY_RELEASE_EVENT,
shift_keycode,
x11rb::CURRENT_TIME,
self.root_window,
0,
0,
0,
)
.map_err(|e| Error::CommitFailed(e.to_string()))?;
}
Ok(())
}
fn send_unicode_input_with_mapping(
&self,
c: char,
mapping: &CachedKeyboardMapping,
) -> Result<()> {
let code = c as u32;
let hex = format!("{:x}", code);
let ctrl_keycode = Self::find_keycode_for_keysym_in_mapping(
XK_CONTROL_L,
&mapping.keysyms,
mapping.keysyms_per_keycode,
mapping.min_keycode,
)
.map(|(kc, _)| kc)
.unwrap_or(37);
let shift_keycode = Self::find_keycode_for_keysym_in_mapping(
XK_SHIFT_L,
&mapping.keysyms,
mapping.keysyms_per_keycode,
mapping.min_keycode,
)
.map(|(kc, _)| kc)
.unwrap_or(50);
let u_keycode = Self::find_keycode_for_keysym_in_mapping(
XK_U,
&mapping.keysyms,
mapping.keysyms_per_keycode,
mapping.min_keycode,
)
.map(|(kc, _)| kc)
.unwrap_or(30);
let space_keycode = Self::find_keycode_for_keysym_in_mapping(
XK_SPACE,
&mapping.keysyms,
mapping.keysyms_per_keycode,
mapping.min_keycode,
)
.map(|(kc, _)| kc)
.unwrap_or(65);
xtest::fake_input(
&self.connection,
KEY_PRESS_EVENT,
ctrl_keycode,
x11rb::CURRENT_TIME,
self.root_window,
0,
0,
0,
)
.map_err(|e| Error::CommitFailed(e.to_string()))?;
xtest::fake_input(
&self.connection,
KEY_PRESS_EVENT,
shift_keycode,
x11rb::CURRENT_TIME,
self.root_window,
0,
0,
0,
)
.map_err(|e| Error::CommitFailed(e.to_string()))?;
xtest::fake_input(
&self.connection,
KEY_PRESS_EVENT,
u_keycode,
x11rb::CURRENT_TIME,
self.root_window,
0,
0,
0,
)
.map_err(|e| Error::CommitFailed(e.to_string()))?;
xtest::fake_input(
&self.connection,
KEY_RELEASE_EVENT,
u_keycode,
x11rb::CURRENT_TIME,
self.root_window,
0,
0,
0,
)
.map_err(|e| Error::CommitFailed(e.to_string()))?;
xtest::fake_input(
&self.connection,
KEY_RELEASE_EVENT,
shift_keycode,
x11rb::CURRENT_TIME,
self.root_window,
0,
0,
0,
)
.map_err(|e| Error::CommitFailed(e.to_string()))?;
xtest::fake_input(
&self.connection,
KEY_RELEASE_EVENT,
ctrl_keycode,
x11rb::CURRENT_TIME,
self.root_window,
0,
0,
0,
)
.map_err(|e| Error::CommitFailed(e.to_string()))?;
for h in hex.chars() {
let keysym = char_to_keysym(h);
if let Some((keycode, needs_shift)) = Self::find_keycode_for_keysym_in_mapping(
keysym,
&mapping.keysyms,
mapping.keysyms_per_keycode,
mapping.min_keycode,
) {
self.send_key_with_mapping(keycode, needs_shift, mapping)?;
}
}
xtest::fake_input(
&self.connection,
KEY_PRESS_EVENT,
space_keycode,
x11rb::CURRENT_TIME,
self.root_window,
0,
0,
0,
)
.map_err(|e| Error::CommitFailed(e.to_string()))?;
xtest::fake_input(
&self.connection,
KEY_RELEASE_EVENT,
space_keycode,
x11rb::CURRENT_TIME,
self.root_window,
0,
0,
0,
)
.map_err(|e| Error::CommitFailed(e.to_string()))?;
Ok(())
}
pub fn set_preedit_string(
&self,
_text: &str,
_cursor_begin: i32,
_cursor_end: i32,
) -> Result<()> {
log_debug!("Preedit not supported in X11 XTest mode");
Ok(())
}
pub fn delete_surrounding_text(&self, before: u32, after: u32) -> Result<()> {
let mapping = self
.keyboard_mapping
.as_ref()
.ok_or_else(|| Error::ConnectionFailed("No keyboard mapping".to_string()))?;
let backspace_keycode = Self::find_keycode_for_keysym_in_mapping(
XK_BACKSPACE,
&mapping.keysyms,
mapping.keysyms_per_keycode,
mapping.min_keycode,
)
.map(|(kc, _)| kc)
.unwrap_or(22);
let delete_keycode = Self::find_keycode_for_keysym_in_mapping(
XK_DELETE,
&mapping.keysyms,
mapping.keysyms_per_keycode,
mapping.min_keycode,
)
.map(|(kc, _)| kc)
.unwrap_or(119);
for _ in 0..before {
self.send_key_with_mapping(backspace_keycode, false, mapping)?;
}
for _ in 0..after {
self.send_key_with_mapping(delete_keycode, false, mapping)?;
}
self.connection
.flush()
.map_err(|e| Error::CommitFailed(e.to_string()))?;
Ok(())
}
pub fn commit(&self, _serial: u32) -> Result<()> {
Ok(())
}
pub fn grab_keyboard(&mut self) -> Result<()> {
Err(Error::ProtocolNotSupported(
"Keyboard grab not supported in X11 XTest mode".to_string(),
))
}
pub fn state(&self) -> InputMethodState {
self.state.clone()
}
}
fn char_to_keysym(c: char) -> Keysym {
let code = c as u32;
if (0x20..=0x7E).contains(&code) {
return code;
}
if (0xA0..=0xFF).contains(&code) {
return code;
}
if code > 0xFF {
return 0x0100_0000 | code;
}
0
}