#![deny(clippy::pedantic)]
#![allow(clippy::cast_lossless)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::cast_possible_wrap)]
#![allow(clippy::cast_sign_loss)]
#![allow(deprecated)]
#![cfg_attr(docsrs, feature(doc_cfg))]
use std::{
error::Error,
fmt::{self, Display, Formatter},
};
use log::{debug, error};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(test)]
use strum_macros::EnumIter;
pub mod agent;
#[cfg_attr(all(unix, not(target_os = "macos")), path = "linux/mod.rs")]
#[cfg_attr(target_os = "macos", path = "macos/mod.rs")]
#[cfg_attr(target_os = "windows", path = "win/mod.rs")]
mod platform;
pub use platform::Enigo;
#[cfg(target_os = "windows")]
pub use platform::EXT;
#[cfg(target_os = "windows")]
pub use platform::set_dpi_awareness;
mod keycodes;
pub use keycodes::Key;
pub const EVENT_MARKER: u32 = 100;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(test, derive(EnumIter))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[doc(alias = "MouseButton")]
pub enum Button {
#[cfg_attr(feature = "serde", serde(alias = "L"))]
#[cfg_attr(feature = "serde", serde(alias = "l"))]
#[default]
Left,
#[cfg_attr(feature = "serde", serde(alias = "M"))]
#[cfg_attr(feature = "serde", serde(alias = "m"))]
Middle,
#[cfg_attr(feature = "serde", serde(alias = "R"))]
#[cfg_attr(feature = "serde", serde(alias = "r"))]
Right,
#[cfg_attr(feature = "serde", serde(alias = "B"))]
#[cfg_attr(feature = "serde", serde(alias = "b"))]
Back,
#[cfg_attr(feature = "serde", serde(alias = "F"))]
#[cfg_attr(feature = "serde", serde(alias = "f"))]
Forward,
#[cfg_attr(feature = "serde", serde(alias = "SU"))]
#[cfg_attr(feature = "serde", serde(alias = "su"))]
ScrollUp,
#[cfg_attr(feature = "serde", serde(alias = "SD"))]
#[cfg_attr(feature = "serde", serde(alias = "sd"))]
ScrollDown,
#[cfg_attr(feature = "serde", serde(alias = "SL"))]
#[cfg_attr(feature = "serde", serde(alias = "sl"))]
ScrollLeft,
#[cfg_attr(feature = "serde", serde(alias = "SR"))]
#[cfg_attr(feature = "serde", serde(alias = "sr"))]
ScrollRight,
}
impl fmt::Debug for Enigo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Enigo")
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Direction {
#[cfg_attr(feature = "serde", serde(alias = "P"))]
#[cfg_attr(feature = "serde", serde(alias = "p"))]
#[cfg_attr(feature = "serde", serde(alias = "Pressed"))]
#[cfg_attr(feature = "serde", serde(alias = "pressed"))]
Press,
#[cfg_attr(feature = "serde", serde(alias = "R"))]
#[cfg_attr(feature = "serde", serde(alias = "r"))]
#[cfg_attr(feature = "serde", serde(alias = "Released"))]
#[cfg_attr(feature = "serde", serde(alias = "released"))]
Release,
#[cfg_attr(feature = "serde", serde(alias = "C"))]
#[cfg_attr(feature = "serde", serde(alias = "c"))]
#[cfg_attr(feature = "serde", serde(alias = "Clicked"))]
#[cfg_attr(feature = "serde", serde(alias = "clicked"))]
#[default]
Click,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Axis {
#[cfg_attr(feature = "serde", serde(alias = "H"))]
#[cfg_attr(feature = "serde", serde(alias = "h"))]
Horizontal,
#[cfg_attr(feature = "serde", serde(alias = "V"))]
#[cfg_attr(feature = "serde", serde(alias = "v"))]
#[default]
Vertical,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Coordinate {
#[doc(alias = "Absolute")]
#[cfg_attr(feature = "serde", serde(alias = "A"))]
#[cfg_attr(feature = "serde", serde(alias = "a"))]
#[default]
Abs,
#[doc(alias = "Relative")]
#[cfg_attr(feature = "serde", serde(alias = "R"))]
#[cfg_attr(feature = "serde", serde(alias = "r"))]
Rel,
}
#[doc(alias = "KeyboardControllable")]
pub trait Keyboard {
#[doc(hidden)]
fn fast_text(&mut self, text: &str) -> InputResult<Option<()>>;
#[doc(alias = "key_sequence")]
fn text(&mut self, text: &str) -> InputResult<()> {
if text.is_empty() {
debug!("The text to enter was empty");
return Ok(()); }
let fast_text_res = self.fast_text(text);
match fast_text_res {
Ok(Some(())) => {
debug!("fast text entry was successful");
Ok(())
}
Ok(None) => {
debug!("fast text entry not available. Trying to enter individual letters now");
for c in text.chars() {
self.key(Key::Unicode(c), Direction::Click)?;
}
Ok(())
}
Err(e) => {
error!("{e}");
Err(e)
}
}
}
#[doc(alias = "key_down", alias = "key_up", alias = "key_click")]
fn key(&mut self, key: Key, direction: Direction) -> InputResult<()>;
#[doc(alias = "Key::Raw")]
fn raw(&mut self, keycode: u16, direction: Direction) -> InputResult<()>;
}
#[doc(alias = "MouseControllable")]
pub trait Mouse {
#[doc(alias = "mouse_down", alias = "mouse_up", alias = "mouse_click")]
fn button(&mut self, button: Button, direction: Direction) -> InputResult<()>;
#[doc(alias = "mouse_move_to", alias = "mouse_move_relative")]
fn move_mouse(&mut self, x: i32, y: i32, coordinate: Coordinate) -> InputResult<()>;
#[doc(alias = "mouse_scroll_x", alias = "mouse_scroll_y")]
fn scroll(&mut self, length: i32, axis: Axis) -> InputResult<()>;
#[cfg_attr(docsrs, doc(cfg(feature = "platform_specific")))]
#[cfg(all(feature = "platform_specific", any(target_os = "macos", doc)))]
#[doc(alias = "mouse_smooth_scroll_x", alias = "mouse_smooth_scroll_y")]
fn smooth_scroll(&mut self, length: i32, axis: Axis) -> InputResult<()> {
unimplemented!()
}
#[doc(alias = "main_display_size")]
fn main_display(&self) -> InputResult<(i32, i32)>;
#[doc(alias = "mouse_location")]
fn location(&self) -> InputResult<(i32, i32)>;
}
pub type InputResult<T> = Result<T, InputError>;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum InputError {
Mapping(String),
Unmapping(String),
NoEmptyKeycodes,
Simulate(&'static str),
InvalidInput(&'static str),
}
impl Display for InputError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let string = match self {
InputError::Mapping(e) => format!("error when mapping keycode to keysym: ({e})"),
InputError::Unmapping(e) => format!("error when unmapping keysym: ({e})"),
InputError::NoEmptyKeycodes => {
"there were no empty keycodes that could be used".to_string()
}
InputError::Simulate(e) => format!("simulating input failed: ({e})"),
InputError::InvalidInput(e) => format!("you tried to simulate invalid input: ({e})"),
};
write!(f, "{string}")
}
}
impl Error for InputError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum NewConError {
EstablishCon(&'static str),
NoPermission,
Reply,
NoEmptyKeycodes,
}
impl Display for NewConError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let string = match self {
NewConError::EstablishCon(e) => format!("no connection could be established: ({e})"),
NewConError::NoPermission => {
"the application does not have the permission to simulate input".to_string()
}
NewConError::Reply => {
"there was an error with the reply from the display server. this should not happen"
.to_string()
}
NewConError::NoEmptyKeycodes => {
"there were no empty keycodes that could be used".to_string()
}
};
write!(f, "{string}")
}
}
impl Error for NewConError {}
#[allow(dead_code)] #[allow(clippy::struct_excessive_bools)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Settings {
pub linux_delay: u32,
pub x11_display: Option<String>,
pub wayland_display: Option<String>,
pub windows_dw_extra_info: Option<usize>,
pub event_source_user_data: Option<i64>,
pub release_keys_when_dropped: bool,
pub open_prompt_to_get_permissions: bool,
pub independent_of_keyboard_state: bool,
pub windows_subject_to_mouse_speed_and_acceleration_level: bool,
}
impl Default for Settings {
fn default() -> Self {
debug!("using default settings");
Self {
linux_delay: 12,
x11_display: None,
wayland_display: None,
windows_dw_extra_info: None,
event_source_user_data: None,
release_keys_when_dropped: true,
open_prompt_to_get_permissions: true,
independent_of_keyboard_state: true,
windows_subject_to_mouse_speed_and_acceleration_level: false,
}
}
}
#[cfg(test)]
mod tests;