use std::{
ffi::{CString, c_char, c_int, c_ulong, c_void},
ptr,
};
use libc::useconds_t;
use log::debug;
use crate::{
Axis, Button, Coordinate, Direction, InputError, InputResult, Key, Keyboard, Mouse, NewConError,
};
use xkeysym::Keysym;
const CURRENT_WINDOW: c_ulong = 0;
const XDO_SUCCESS: c_int = 0;
type Window = c_ulong;
type Xdo = *const c_void;
#[link(name = "xdo")]
unsafe extern "C" {
fn xdo_free(xdo: Xdo);
fn xdo_new(display: *const c_char) -> Xdo;
fn xdo_click_window(xdo: Xdo, window: Window, button: c_int) -> c_int;
fn xdo_mouse_down(xdo: Xdo, window: Window, button: c_int) -> c_int;
fn xdo_mouse_up(xdo: Xdo, window: Window, button: c_int) -> c_int;
fn xdo_move_mouse(xdo: Xdo, x: c_int, y: c_int, screen: c_int) -> c_int;
fn xdo_move_mouse_relative(xdo: Xdo, x: c_int, y: c_int) -> c_int;
fn xdo_enter_text_window(
xdo: Xdo,
window: Window,
string: *const c_char,
delay: useconds_t,
) -> c_int;
fn xdo_send_keysequence_window(
xdo: Xdo,
window: Window,
string: *const c_char,
delay: useconds_t,
) -> c_int;
fn xdo_send_keysequence_window_down(
xdo: Xdo,
window: Window,
string: *const c_char,
delay: useconds_t,
) -> c_int;
fn xdo_send_keysequence_window_up(
xdo: Xdo,
window: Window,
string: *const c_char,
delay: useconds_t,
) -> c_int;
fn xdo_get_viewport_dimensions(
xdo: Xdo,
width: *mut c_int,
height: *mut c_int,
screen: c_int,
) -> c_int;
fn xdo_get_mouse_location2(
xdo: Xdo,
x: *mut c_int,
y: *mut c_int,
screen: *mut c_int,
window: *mut Window,
) -> c_int;
}
fn mousebutton(button: Button) -> c_int {
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,
}
}
pub struct Con {
xdo: Xdo,
delay: u32, }
unsafe impl Send for Con {}
impl Con {
pub fn new(dyp_name: Option<&str>, delay: u32) -> Result<Self, NewConError> {
debug!("using xdo");
let xdo = match dyp_name {
Some(name) => {
let Ok(string) = CString::new(name) else {
return Err(NewConError::EstablishCon(
"the display name contained a null byte",
));
};
unsafe { xdo_new(string.as_ptr()) }
}
None => unsafe { xdo_new(ptr::null()) },
};
if xdo.is_null() {
return Err(NewConError::EstablishCon(
"establishing a connection to the display name was unsuccessful",
));
}
Ok(Self {
xdo,
delay: delay * 1000,
})
}
#[must_use]
pub fn delay(&self) -> u32 {
self.delay / 1000
}
pub fn set_delay(&mut self, delay: u32) {
self.delay = delay * 1000;
}
}
impl Drop for Con {
fn drop(&mut self) {
unsafe {
xdo_free(self.xdo);
}
}
}
impl Keyboard for Con {
fn fast_text(&mut self, text: &str) -> InputResult<Option<()>> {
let Ok(string) = CString::new(text) else {
return Err(InputError::InvalidInput(
"the text to enter contained a NULL byte ('\\0’), which is not allowed",
));
};
debug!(
"xdo_enter_text_window with string {string:?}, delay {}",
self.delay
);
let res = unsafe {
xdo_enter_text_window(
self.xdo,
CURRENT_WINDOW,
string.as_ptr(),
self.delay as useconds_t,
)
};
if res != XDO_SUCCESS {
return Err(InputError::Simulate("unable to enter text"));
}
Ok(Some(()))
}
fn key(&mut self, key: Key, direction: Direction) -> InputResult<()> {
let keysym = Keysym::from(key);
let keysym_name = format!("{keysym:?}");
let keysym_name = keysym_name.replace("XK_", "");
let Ok(string) = CString::new(keysym_name) else {
return Err(InputError::InvalidInput(
"the name of the keysym contained a null byte",
));
};
let res = match direction {
Direction::Click => {
debug!(
"xdo_send_keysequence_window with string {string:?}, delay {}",
self.delay
);
unsafe {
xdo_send_keysequence_window(
self.xdo,
CURRENT_WINDOW,
string.as_ptr(),
self.delay as useconds_t,
)
}
}
Direction::Press => {
debug!(
"xdo_send_keysequence_window_down with string {string:?}, delay {}",
self.delay
);
unsafe {
xdo_send_keysequence_window_down(
self.xdo,
CURRENT_WINDOW,
string.as_ptr(),
self.delay as useconds_t,
)
}
}
Direction::Release => {
debug!(
"xdo_send_keysequence_window_up with string {string:?}, delay {}",
self.delay
);
unsafe {
xdo_send_keysequence_window_up(
self.xdo,
CURRENT_WINDOW,
string.as_ptr(),
self.delay as useconds_t,
)
}
}
};
if res != XDO_SUCCESS {
return Err(InputError::Simulate("unable to enter key"));
}
Ok(())
}
fn raw(&mut self, _keycode: u16, _direction: Direction) -> InputResult<()> {
todo!("You cant enter raw keycodes with xdotool")
}
}
impl Mouse for Con {
fn button(&mut self, button: Button, direction: Direction) -> InputResult<()> {
let button = mousebutton(button);
let res = match direction {
Direction::Press => {
debug!("xdo_mouse_down with mouse button {button}");
unsafe { xdo_mouse_down(self.xdo, CURRENT_WINDOW, button) }
}
Direction::Release => {
debug!("xdo_mouse_up with mouse button {button}");
unsafe { xdo_mouse_up(self.xdo, CURRENT_WINDOW, button) }
}
Direction::Click => {
debug!("xdo_click_window with mouse button {button}");
unsafe { xdo_click_window(self.xdo, CURRENT_WINDOW, button) }
}
};
if res != XDO_SUCCESS {
return Err(InputError::Simulate("unable to enter mouse button"));
}
Ok(())
}
fn move_mouse(&mut self, x: i32, y: i32, coordinate: Coordinate) -> InputResult<()> {
let res = match coordinate {
Coordinate::Rel => {
debug!("xdo_move_mouse_relative with x {x}, y {y}");
unsafe { xdo_move_mouse_relative(self.xdo, x as c_int, y as c_int) }
}
Coordinate::Abs => {
debug!("xdo_move_mouse with mouse button with x {x}, y {y}");
unsafe { xdo_move_mouse(self.xdo, x as c_int, y as c_int, 0) }
}
};
if res != XDO_SUCCESS {
return Err(InputError::Simulate("unable to move the mouse"));
}
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)> {
const MAIN_SCREEN: i32 = 0;
let mut width = 0;
let mut height = 0;
debug!("xdo_get_viewport_dimensions");
let res = unsafe {
xdo_get_viewport_dimensions(self.xdo, &raw mut width, &raw mut height, MAIN_SCREEN)
};
if res != XDO_SUCCESS {
return Err(InputError::Simulate("unable to get the main display"));
}
Ok((width, height))
}
fn location(&self) -> InputResult<(i32, i32)> {
let mut x = 0;
let mut y = 0;
let mut unused_screen_index = 0;
let mut unused_window_index = CURRENT_WINDOW;
debug!("xdo_get_mouse_location2");
let res = unsafe {
xdo_get_mouse_location2(
self.xdo,
&raw mut x,
&raw mut y,
&raw mut unused_screen_index,
&raw mut unused_window_index,
)
};
if res != XDO_SUCCESS {
return Err(InputError::Simulate(
"unable to get the position of the mouse",
));
}
Ok((x, y))
}
}