use std::collections::VecDeque;
use std::convert::TryInto;
use log::{debug, error, trace, warn};
use x11rb::{
connection::Connection,
protocol::{
randr::ConnectionExt as _,
xinput::DeviceUse,
xproto::{ConnectionExt as _, GetKeyboardMappingReply, GetModifierMappingReply, Screen},
xtest::ConnectionExt as _,
},
rust_connection::{ConnectError, ConnectionError, DefaultStream, ReplyError, RustConnection},
wrapper::ConnectionExt as _,
};
use super::keymap::{Bind, KeyMap, Keysym};
use crate::{
Axis, Button, Coordinate, Direction, InputError, InputResult, Key, Keyboard, Mouse, NewConError,
};
type CompositorConnection = RustConnection<DefaultStream>;
pub type Keycode = u8;
pub struct Con {
connection: CompositorConnection,
screen: Screen,
keymap: KeyMap<Keycode>,
modifiers: [Vec<Keycode>; 8],
delay: u32, }
impl From<ConnectionError> for NewConError {
fn from(error: ConnectionError) -> Self {
error!("{error:?}");
Self::EstablishCon("failed to get the modifier map")
}
}
impl From<ConnectError> for NewConError {
fn from(error: ConnectError) -> Self {
error!("{error:?}");
Self::EstablishCon("failed to establish the connection")
}
}
impl From<ReplyError> for NewConError {
fn from(error: ReplyError) -> Self {
error!("{error:?}");
Self::Reply
}
}
impl Con {
pub fn new(dpy_name: Option<&str>, delay: u32) -> Result<Con, NewConError> {
debug!("using x11rb");
let (connection, screen_idx) = x11rb::connect(dpy_name)?;
let setup = connection.setup();
let screen = setup.roots[screen_idx].clone();
let min_keycode = setup.min_keycode;
let max_keycode = setup.max_keycode;
let (keysyms_per_keycode, keysyms) =
Self::get_keyboard_mapping(&connection, min_keycode, max_keycode)?; let unused_keycodes =
Self::unused_keycodes(min_keycode, max_keycode, keysyms_per_keycode, &keysyms);
if unused_keycodes.is_empty() {
return Err(NewConError::NoEmptyKeycodes);
}
let keymap = KeyMap::new(
min_keycode,
max_keycode,
unused_keycodes,
keysyms_per_keycode,
keysyms,
);
let modifiers = Self::find_modifier_keycodes(&connection)?;
Ok(Con {
connection,
screen,
keymap,
modifiers,
delay,
})
}
#[must_use]
pub fn delay(&self) -> u32 {
self.delay
}
pub fn set_delay(&mut self, delay: u32) {
self.delay = delay;
}
fn get_keyboard_mapping(
connection: &CompositorConnection,
keycode_min: Keycode,
keycode_max: Keycode,
) -> Result<(u8, Vec<u32>), ReplyError> {
let GetKeyboardMappingReply {
keysyms_per_keycode,
keysyms,
..
} = connection
.get_keyboard_mapping(keycode_min, keycode_max - keycode_min + 1)?
.reply()?;
Ok((keysyms_per_keycode, keysyms))
}
fn unused_keycodes(
keycode_min: Keycode,
keycode_max: Keycode,
keysyms_per_keycode: u8,
keysyms: &[u32],
) -> VecDeque<Keycode> {
let mut unused_keycodes: VecDeque<Keycode> =
VecDeque::with_capacity((keycode_max - keycode_min) as usize);
trace!("initial keymap:");
let keysyms = keysyms.chunks(keysyms_per_keycode as usize);
for (syms, kc) in keysyms.zip(keycode_min..=keycode_max) {
if log::log_enabled!(log::Level::Trace) {
let syms_name: Vec<Keysym> = syms.iter().map(|&s| Keysym::from(s)).collect();
trace!("{kc}: {syms_name:?}");
}
if syms.iter().all(|&s| s == Keysym::NoSymbol.raw()) && kc != 8 {
unused_keycodes.push_back(kc);
}
}
debug!("unused keycodes: {unused_keycodes:?}");
unused_keycodes
}
fn find_modifier_keycodes(
connection: &CompositorConnection,
) -> Result<[Vec<Keycode>; 8], ReplyError> {
let modifier_reply = connection.get_modifier_mapping()?.reply()?;
let keycodes_per_modifier = modifier_reply.keycodes_per_modifier() as usize;
let GetModifierMappingReply {
keycodes: modifiers,
..
} = modifier_reply;
let mut modifiers_array: [Vec<Keycode>; 8] = Default::default(); let modifier_mapping = modifiers.chunks(keycodes_per_modifier);
if modifier_mapping.len() > 8 {
error!(
"the associated keycodes of {} modifiers were returned! Only 8 were expected",
modifier_mapping.len()
);
return Err(ReplyError::ConnectionError(ConnectionError::UnknownError));
}
for (mod_no, mod_keycodes) in modifier_mapping.enumerate() {
let keycodes: Vec<_> = mod_keycodes.iter().copied().filter(|&kc| kc != 0).collect();
if keycodes.is_empty() {
warn!("modifier_no: {mod_no} is unmapped");
}
modifiers_array[mod_no] = keycodes;
}
debug!("the keycodes associated with the modifiers are:\n{modifiers_array:?}");
Ok(modifiers_array)
}
fn device_id(&self, usage: DeviceUse) -> InputResult<u8> {
x11rb::protocol::xinput::list_input_devices(&self.connection)
.map_err(|e| {
error!("{e}");
InputError::Simulate("error when listing input devices with x11rb: {e:?}")
})?
.reply()
.map_err(|e| {
error!("{e}");
InputError::Simulate(
"error with the reply from listing input devices with x11rb: {e:?}",
)
})?
.devices
.iter()
.find(|d| d.device_use == usage)
.map_or_else(
|| {
Err(InputError::Simulate(
"error with the reply from listing input devices with x11rb: {e:?}",
))
},
|d| Ok(d.device_id),
)
}
}
impl Drop for Con {
fn drop(&mut self) {
debug!("x11rb connection was dropped");
for &keycode in self.keymap.keymap_mapping.additionally_mapped.values() {
match self.connection.bind_key(keycode, Keysym::NoSymbol) {
Ok(()) => debug!("unmapped keycode {keycode:?}"),
Err(e) => error!("unable to unmap keycode {keycode:?}. {e:?}"),
}
}
}
}
impl Bind<Keycode> for CompositorConnection {
fn bind_key(&self, keycode: Keycode, keysym: Keysym) -> Result<(), ()> {
self.change_keyboard_mapping(1, keycode, 2, &[keysym.raw(), keysym.raw()])
.map_err(|e| error!("error when changing the keyboard mapping with x11rb: {e:?}"))?;
self.sync().map_err(|e| error!("error when syncing with X server using x11rb after the keyboard mapping was changed: {e:?}"))
}
}
impl Keyboard for Con {
fn fast_text(&mut self, _text: &str) -> InputResult<Option<()>> {
warn!("fast text entry is not possible on X11");
Ok(None)
}
fn key(&mut self, key: Key, direction: Direction) -> InputResult<()> {
let keycode = self.keymap.key_to_keycode(&self.connection, key)?;
if log::log_enabled!(log::Level::Debug) {
for (mod_idx, mod_keycodes) in self.modifiers.iter().enumerate() {
if mod_keycodes.contains(&keycode) {
debug!("the key is modifier no: {mod_idx}");
}
}
}
self.raw(keycode.into(), direction)
}
fn raw(&mut self, keycode: u16, direction: Direction) -> InputResult<()> {
let Ok(keycode) = keycode.try_into() else {
return Err(InputError::InvalidInput(
"Keycode was too large. It has to fit in u8 on X11",
));
};
let time = self.keymap.pending_delays();
let root = self.screen.root;
let root_x = 0;
let root_y = 0;
let deviceid = self.device_id(DeviceUse::IS_X_KEYBOARD)?;
debug!("xtest_fake_input with keycode {keycode}, deviceid {deviceid}, delay {time}");
if direction == Direction::Press || direction == Direction::Click {
self.connection
.xtest_fake_input(
x11rb::protocol::xproto::KEY_PRESS_EVENT,
keycode,
time,
root,
root_x,
root_y,
deviceid,
)
.map_err(|e| {
error!("{e}");
InputError::Simulate("error when using xtest_fake_input with x11rb: {e:?}")
})?;
trace!("press");
}
if direction == Direction::Release || direction == Direction::Click {
self.connection
.xtest_fake_input(
x11rb::protocol::xproto::KEY_RELEASE_EVENT,
keycode,
time, root,
root_x,
root_y,
deviceid,
)
.map_err(|e| {
error!("{e}");
InputError::Simulate("error when using xtest_fake_input with x11rb: {e:?}")
})?;
trace!("released");
}
self.connection.sync()
.map_err(|e| {
error!("{e}");
InputError::Simulate("error when syncing with X server using x11rb after the keyboard mapping was changed: {e:?}")
})?;
self.keymap.key(keycode, direction);
Ok(())
}
}
impl Mouse for Con {
fn button(&mut self, button: Button, direction: Direction) -> InputResult<()> {
let detail = match button {
Button::Left => 1,
Button::Middle => 2,
Button::Right => 3,
Button::ScrollUp => 4,
Button::ScrollDown => 5,
Button::ScrollLeft => 6,
Button::ScrollRight => 7,
Button::Back => 8,
Button::Forward => 9,
};
let time = self.delay;
let root = self.screen.root;
let root_x = 0;
let root_y = 0;
let deviceid = self.device_id(DeviceUse::IS_X_POINTER)?;
debug!("xtest_fake_input with button {detail}, deviceid {deviceid}, delay {time}");
if direction == Direction::Press || direction == Direction::Click {
self.connection
.xtest_fake_input(
x11rb::protocol::xproto::BUTTON_PRESS_EVENT,
detail,
time,
root,
root_x,
root_y,
deviceid,
)
.map_err(|e| {
error!("{e}");
InputError::Simulate("error when using xtest_fake_input with x11rb: {e:?}")
})?;
}
if direction == Direction::Release || direction == Direction::Click {
if direction == Direction::Click {
}
self.connection
.xtest_fake_input(
x11rb::protocol::xproto::BUTTON_RELEASE_EVENT,
detail,
time,
root,
root_x,
root_y,
deviceid,
)
.map_err(|e| {
error!("{e}");
InputError::Simulate("error when using xtest_fake_input with x11rb: {e:?}")
})?;
}
self.connection.sync()
.map_err(|e| {
error!("{e}");
InputError::Simulate("error when syncing with X server using x11rb after the keyboard mapping was changed: {e:?}")
})?;
Ok(())
}
fn move_mouse(&mut self, x: i32, y: i32, coordinate: Coordinate) -> InputResult<()> {
let type_ = x11rb::protocol::xproto::MOTION_NOTIFY_EVENT;
let detail = match coordinate {
Coordinate::Rel => 1,
Coordinate::Abs => 0,
};
let time = x11rb::CURRENT_TIME;
let root = x11rb::NONE;
let Ok(root_x) = x.try_into() else {
return Err(InputError::InvalidInput(
"the coordinates cannot be negative and must fit in i16",
));
};
let Ok(root_y) = y.try_into() else {
return Err(InputError::InvalidInput(
"the coordinates cannot be negative and must fit in i16",
));
};
let deviceid = self.device_id(DeviceUse::IS_X_POINTER)?;
debug!(
"xtest_fake_input with coordinate {detail}, deviceid {deviceid}, x {root_x}, y {root_y}, delay {time}"
);
self.connection
.xtest_fake_input(type_, detail, time, root, root_x, root_y, deviceid) .map_err(|e| {
error!("{e}");
InputError::Simulate("error when using xtest_fake_input with x11rb: {e:?}")
})?;
self.connection.sync()
.map_err(|e| {
error!("{e}");
InputError::Simulate("error when syncing with X server using x11rb after the keyboard mapping was changed: {e:?}")
})?;
Ok(())
}
fn scroll(&mut self, length: i32, axis: Axis) -> InputResult<()> {
let button = match (length.is_positive(), axis) {
(true, Axis::Vertical) => Button::ScrollDown,
(false, Axis::Vertical) => Button::ScrollUp,
(true, Axis::Horizontal) => Button::ScrollRight,
(false, Axis::Horizontal) => Button::ScrollLeft,
};
for _ in 0..length.abs() {
self.button(button, Direction::Click)?;
}
Ok(())
}
fn main_display(&self) -> InputResult<(i32, i32)> {
let main_display = self
.connection
.randr_get_screen_resources(self.screen.root)
.map_err(|e| {
error!("{e}");
InputError::Simulate(
"error when requesting randr_get_screen_resources with x11rb: {e:?}",
)
})?
.reply()
.map_err(|e| {
error!("{e}");
InputError::Simulate(
"error with the reply of randr_get_screen_resources with x11rb: {e:?}",
)
})?
.modes[0];
Ok((main_display.width as i32, main_display.height as i32))
}
fn location(&self) -> InputResult<(i32, i32)> {
let reply = self
.connection
.query_pointer(self.screen.root)
.map_err(|e| {
error!("{e}");
InputError::Simulate("error when requesting query_pointer with x11rb: {e:?}")
})?
.reply()
.map_err(|e| {
error!("{e}");
InputError::Simulate("error with the reply of query_pointer with x11rb: {e:?}")
})?;
Ok((reply.root_x as i32, reply.root_y as i32))
}
}