source code

This commit is contained in:
rustdesk
2021-03-29 15:59:14 +08:00
parent 002fce136c
commit d1013487e2
175 changed files with 35074 additions and 2 deletions

184
libs/enigo/src/dsl.rs Normal file
View File

@@ -0,0 +1,184 @@
use crate::{Key, KeyboardControllable};
use std::error::Error;
use std::fmt;
/// An error that can occur when parsing DSL
#[derive(Debug, PartialEq, Eq)]
pub enum ParseError {
/// When a tag doesn't exist.
/// Example: {+TEST}{-TEST}
/// ^^^^ ^^^^
UnknownTag(String),
/// When a { is encountered inside a {TAG}.
/// Example: {+HELLO{WORLD}
/// ^
UnexpectedOpen,
/// When a { is never matched with a }.
/// Example: {+SHIFT}Hello{-SHIFT
/// ^
UnmatchedOpen,
/// Opposite of UnmatchedOpen.
/// Example: +SHIFT}Hello{-SHIFT}
/// ^
UnmatchedClose,
}
impl Error for ParseError {
fn description(&self) -> &str {
match *self {
ParseError::UnknownTag(_) => "Unknown tag",
ParseError::UnexpectedOpen => "Unescaped open bracket ({) found inside tag name",
ParseError::UnmatchedOpen => "Unmatched open bracket ({). No matching close (})",
ParseError::UnmatchedClose => "Unmatched close bracket (}). No previous open ({)",
}
}
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.to_string())
}
}
/// Evaluate the DSL. This tokenizes the input and presses the keys.
pub fn eval<K>(enigo: &mut K, input: &str) -> Result<(), ParseError>
where
K: KeyboardControllable,
{
for token in tokenize(input)? {
match token {
Token::Sequence(buffer) => {
for key in buffer.chars() {
enigo.key_click(Key::Layout(key));
}
}
Token::Unicode(buffer) => enigo.key_sequence(&buffer),
Token::KeyUp(key) => enigo.key_up(key),
Token::KeyDown(key) => enigo.key_down(key).unwrap_or(()),
}
}
Ok(())
}
#[derive(Debug, PartialEq, Eq)]
enum Token {
Sequence(String),
Unicode(String),
KeyUp(Key),
KeyDown(Key),
}
fn tokenize(input: &str) -> Result<Vec<Token>, ParseError> {
let mut unicode = false;
let mut tokens = Vec::new();
let mut buffer = String::new();
let mut iter = input.chars().peekable();
fn flush(tokens: &mut Vec<Token>, buffer: String, unicode: bool) {
if !buffer.is_empty() {
if unicode {
tokens.push(Token::Unicode(buffer));
} else {
tokens.push(Token::Sequence(buffer));
}
}
}
while let Some(c) = iter.next() {
if c == '{' {
match iter.next() {
Some('{') => buffer.push('{'),
Some(mut c) => {
flush(&mut tokens, buffer, unicode);
buffer = String::new();
let mut tag = String::new();
loop {
tag.push(c);
match iter.next() {
Some('{') => match iter.peek() {
Some(&'{') => {
iter.next();
c = '{'
}
_ => return Err(ParseError::UnexpectedOpen),
},
Some('}') => match iter.peek() {
Some(&'}') => {
iter.next();
c = '}'
}
_ => break,
},
Some(new) => c = new,
None => return Err(ParseError::UnmatchedOpen),
}
}
match &*tag {
"+UNICODE" => unicode = true,
"-UNICODE" => unicode = false,
"+SHIFT" => tokens.push(Token::KeyDown(Key::Shift)),
"-SHIFT" => tokens.push(Token::KeyUp(Key::Shift)),
"+CTRL" => tokens.push(Token::KeyDown(Key::Control)),
"-CTRL" => tokens.push(Token::KeyUp(Key::Control)),
"+META" => tokens.push(Token::KeyDown(Key::Meta)),
"-META" => tokens.push(Token::KeyUp(Key::Meta)),
"+ALT" => tokens.push(Token::KeyDown(Key::Alt)),
"-ALT" => tokens.push(Token::KeyUp(Key::Alt)),
_ => return Err(ParseError::UnknownTag(tag)),
}
}
None => return Err(ParseError::UnmatchedOpen),
}
} else if c == '}' {
match iter.next() {
Some('}') => buffer.push('}'),
_ => return Err(ParseError::UnmatchedClose),
}
} else {
buffer.push(c);
}
}
flush(&mut tokens, buffer, unicode);
Ok(tokens)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn success() {
assert_eq!(
tokenize("{{Hello World!}} {+CTRL}hi{-CTRL}"),
Ok(vec![
Token::Sequence("{Hello World!} ".into()),
Token::KeyDown(Key::Control),
Token::Sequence("hi".into()),
Token::KeyUp(Key::Control)
])
);
}
#[test]
fn unexpected_open() {
assert_eq!(tokenize("{hello{}world}"), Err(ParseError::UnexpectedOpen));
}
#[test]
fn unmatched_open() {
assert_eq!(
tokenize("{this is going to fail"),
Err(ParseError::UnmatchedOpen)
);
}
#[test]
fn unmatched_close() {
assert_eq!(
tokenize("{+CTRL}{{this}} is going to fail}"),
Err(ParseError::UnmatchedClose)
);
}
}

525
libs/enigo/src/lib.rs Normal file
View File

@@ -0,0 +1,525 @@
//! Enigo lets you simulate mouse and keyboard input-events as if they were
//! made by the actual hardware. The goal is to make it available on different
//! operating systems like Linux, macOS and Windows possibly many more but
//! [Redox](https://redox-os.org/) and *BSD are planned. Please see the
//! [Repo](https://github.com/enigo-rs/enigo) for the current status.
//!
//! I consider this library in an early alpha status, the API will change in
//! in the future. The keyboard handling is far from being very usable. I plan
//! to build a simple
//! [DSL](https://en.wikipedia.org/wiki/Domain-specific_language)
//! that will resemble something like:
//!
//! `"hello {+SHIFT}world{-SHIFT} and break line{ENTER}"`
//!
//! The current status is that you can just print
//! [unicode](http://unicode.org/)
//! characters like [emoji](http://getemoji.com/) without the `{+SHIFT}`
//! [DSL](https://en.wikipedia.org/wiki/Domain-specific_language)
//! or any other "special" key on the Linux, macOS and Windows operating system.
//!
//! Possible use cases could be for testing user interfaces on different
//! plattforms,
//! building remote control applications or just automating tasks for user
//! interfaces unaccessible by a public API or scripting laguage.
//!
//! For the keyboard there are currently two modes you can use. The first mode
//! is represented by the [key_sequence]() function
//! its purpose is to simply write unicode characters. This is independent of
//! the keyboardlayout. Please note that
//! you're not be able to use modifier keys like Control
//! to influence the outcome. If you want to use modifier keys to e.g.
//! copy/paste
//! use the Layout variant. Please note that this is indeed layout dependent.
//! # Examples
//! ```no_run
//! use enigo::*;
//! let mut enigo = Enigo::new();
//! //paste
//! enigo.key_down(Key::Control);
//! enigo.key_click(Key::Layout('v'));
//! enigo.key_up(Key::Control);
//! ```
//!
//! ```no_run
//! use enigo::*;
//! let mut enigo = Enigo::new();
//! enigo.mouse_move_to(500, 200);
//! enigo.mouse_down(MouseButton::Left);
//! enigo.mouse_move_relative(100, 100);
//! enigo.mouse_up(MouseButton::Left);
//! enigo.key_sequence("hello world");
//! ```
#![deny(missing_docs)]
#[cfg(target_os = "macos")]
#[macro_use]
extern crate objc;
// TODO(dustin) use interior mutability not &mut self
#[cfg(target_os = "windows")]
mod win;
#[cfg(target_os = "windows")]
pub use win::Enigo;
#[cfg(target_os = "macos")]
mod macos;
#[cfg(target_os = "macos")]
pub use macos::Enigo;
#[cfg(target_os = "linux")]
mod linux;
#[cfg(target_os = "linux")]
pub use crate::linux::Enigo;
/// DSL parser module
pub mod dsl;
#[cfg(feature = "with_serde")]
#[macro_use]
extern crate serde_derive;
#[cfg(feature = "with_serde")]
extern crate serde;
///
pub type ResultType = std::result::Result<(), Box<dyn std::error::Error>>;
#[cfg_attr(feature = "with_serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq)]
/// MouseButton represents a mouse button,
/// and is used in for example
/// [mouse_click](trait.MouseControllable.html#tymethod.mouse_click).
/// WARNING: Types with the prefix Scroll
/// IS NOT intended to be used, and may not work on
/// all operating systems.
pub enum MouseButton {
/// Left mouse button
Left,
/// Middle mouse button
Middle,
/// Right mouse button
Right,
/// Scroll up button
ScrollUp,
/// Left right button
ScrollDown,
/// Left right button
ScrollLeft,
/// Left right button
ScrollRight,
}
/// Representing an interface and a set of mouse functions every
/// operating system implementation _should_ implement.
pub trait MouseControllable {
/// Lets the mouse cursor move to the specified x and y coordinates.
///
/// The topleft corner of your monitor screen is x=0 y=0. Move
/// the cursor down the screen by increasing the y and to the right
/// by increasing x coordinate.
///
/// # Example
///
/// ```no_run
/// use enigo::*;
/// let mut enigo = Enigo::new();
/// enigo.mouse_move_to(500, 200);
/// ```
fn mouse_move_to(&mut self, x: i32, y: i32);
/// Lets the mouse cursor move the specified amount in the x and y
/// direction.
///
/// The amount specified in the x and y parameters are added to the
/// current location of the mouse cursor. A positive x values lets
/// the mouse cursor move an amount of `x` pixels to the right. A negative
/// value for `x` lets the mouse cursor go to the left. A positive value
/// of y
/// lets the mouse cursor go down, a negative one lets the mouse cursor go
/// up.
///
/// # Example
///
/// ```no_run
/// use enigo::*;
/// let mut enigo = Enigo::new();
/// enigo.mouse_move_relative(100, 100);
/// ```
fn mouse_move_relative(&mut self, x: i32, y: i32);
/// Push down one of the mouse buttons
///
/// Push down the mouse button specified by the parameter `button` of
/// type [MouseButton](enum.MouseButton.html)
/// and holds it until it is released by
/// [mouse_up](trait.MouseControllable.html#tymethod.mouse_up).
/// Calls to [mouse_move_to](trait.MouseControllable.html#tymethod.
/// mouse_move_to) or
/// [mouse_move_relative](trait.MouseControllable.html#tymethod.
/// mouse_move_relative)
/// will work like expected and will e.g. drag widgets or highlight text.
///
/// # Example
///
/// ```no_run
/// use enigo::*;
/// let mut enigo = Enigo::new();
/// enigo.mouse_down(MouseButton::Left);
/// ```
fn mouse_down(&mut self, button: MouseButton) -> ResultType;
/// Lift up a pushed down mouse button
///
/// Lift up a previously pushed down button (by invoking
/// [mouse_down](trait.MouseControllable.html#tymethod.mouse_down)).
/// If the button was not pushed down or consecutive calls without
/// invoking [mouse_down](trait.MouseControllable.html#tymethod.mouse_down)
/// will emit lift up events. It depends on the
/// operating system whats actually happening my guess is it will just
/// get ignored.
///
/// # Example
///
/// ```no_run
/// use enigo::*;
/// let mut enigo = Enigo::new();
/// enigo.mouse_up(MouseButton::Right);
/// ```
fn mouse_up(&mut self, button: MouseButton);
/// Click a mouse button
///
/// it's esentially just a consecutive invokation of
/// [mouse_down](trait.MouseControllable.html#tymethod.mouse_down) followed
/// by a [mouse_up](trait.MouseControllable.html#tymethod.mouse_up). Just
/// for
/// convenience.
///
/// # Example
///
/// ```no_run
/// use enigo::*;
/// let mut enigo = Enigo::new();
/// enigo.mouse_click(MouseButton::Right);
/// ```
fn mouse_click(&mut self, button: MouseButton);
/// Scroll the mouse (wheel) left or right
///
/// Positive numbers for length lets the mouse wheel scroll to the right
/// and negative ones to the left. The value that is specified translates
/// to `lines` defined by the operating system and is essentially one 15°
/// (click)rotation on the mouse wheel. How many lines it moves depends
/// on the current setting in the operating system.
///
/// # Example
///
/// ```no_run
/// use enigo::*;
/// let mut enigo = Enigo::new();
/// enigo.mouse_scroll_x(2);
/// ```
fn mouse_scroll_x(&mut self, length: i32);
/// Scroll the mouse (wheel) up or down
///
/// Positive numbers for length lets the mouse wheel scroll down
/// and negative ones up. The value that is specified translates
/// to `lines` defined by the operating system and is essentially one 15°
/// (click)rotation on the mouse wheel. How many lines it moves depends
/// on the current setting in the operating system.
///
/// # Example
///
/// ```no_run
/// use enigo::*;
/// let mut enigo = Enigo::new();
/// enigo.mouse_scroll_y(2);
/// ```
fn mouse_scroll_y(&mut self, length: i32);
}
/// A key on the keyboard.
/// For alphabetical keys, use Key::Layout for a system independent key.
/// If a key is missing, you can use the raw keycode with Key::Raw.
#[cfg_attr(feature = "with_serde", derive(Serialize, Deserialize))]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Key {
/// alt key on Linux and Windows (option key on macOS)
Alt,
/// backspace key
Backspace,
/// caps lock key
CapsLock,
#[deprecated(since = "0.0.12", note = "now renamed to Meta")]
/// command key on macOS (super key on Linux, windows key on Windows)
Command,
/// control key
Control,
/// delete key
Delete,
/// down arrow key
DownArrow,
/// end key
End,
/// escape key (esc)
Escape,
/// F1 key
F1,
/// F10 key
F10,
/// F11 key
F11,
/// F12 key
F12,
/// F2 key
F2,
/// F3 key
F3,
/// F4 key
F4,
/// F5 key
F5,
/// F6 key
F6,
/// F7 key
F7,
/// F8 key
F8,
/// F9 key
F9,
/// home key
Home,
/// left arrow key
LeftArrow,
/// meta key (also known as "windows", "super", and "command")
Meta,
/// option key on macOS (alt key on Linux and Windows)
Option,
/// page down key
PageDown,
/// page up key
PageUp,
/// return key
Return,
/// right arrow key
RightArrow,
/// shift key
Shift,
/// space key
Space,
#[deprecated(since = "0.0.12", note = "now renamed to Meta")]
/// super key on linux (command key on macOS, windows key on Windows)
Super,
/// tab key (tabulator)
Tab,
/// up arrow key
UpArrow,
#[deprecated(since = "0.0.12", note = "now renamed to Meta")]
/// windows key on Windows (super key on Linux, command key on macOS)
Windows,
///
Numpad0,
///
Numpad1,
///
Numpad2,
///
Numpad3,
///
Numpad4,
///
Numpad5,
///
Numpad6,
///
Numpad7,
///
Numpad8,
///
Numpad9,
///
Cancel,
///
Clear,
///
Menu,
///
Pause,
///
Kana,
///
Hangul,
///
Junja,
///
Final,
///
Hanja,
///
Kanji,
///
Convert,
///
Select,
///
Print,
///
Execute,
///
Snapshot,
///
Insert,
///
Help,
///
Sleep,
///
Separator,
///
VolumeUp,
///
VolumeDown,
///
Mute,
///
Scroll,
/// scroll lock
NumLock,
///
RWin,
///
Apps,
///
Multiply,
///
Add,
///
Subtract,
///
Decimal,
///
Divide,
///
Equals,
///
NumpadEnter,
///
/// Function, /// mac
/// keyboard layout dependent key
Layout(char),
/// raw keycode eg 0x38
Raw(u16),
}
/// Representing an interface and a set of keyboard functions every
/// operating system implementation _should_ implement.
pub trait KeyboardControllable {
/// Types the string parsed with DSL.
///
/// Typing {+SHIFT}hello{-SHIFT} becomes HELLO.
/// TODO: Full documentation
fn key_sequence_parse(&mut self, sequence: &str)
where
Self: Sized,
{
self.key_sequence_parse_try(sequence)
.expect("Could not parse sequence");
}
/// Same as key_sequence_parse except returns any errors
fn key_sequence_parse_try(&mut self, sequence: &str) -> Result<(), dsl::ParseError>
where
Self: Sized,
{
dsl::eval(self, sequence)
}
/// Types the string
///
/// Emits keystrokes such that the given string is inputted.
///
/// You can use many unicode here like: ❤️. This works
/// regadless of the current keyboardlayout.
///
/// # Example
///
/// ```no_run
/// use enigo::*;
/// let mut enigo = Enigo::new();
/// enigo.key_sequence("hello world ❤️");
/// ```
fn key_sequence(&mut self, sequence: &str);
/// presses a given key down
fn key_down(&mut self, key: Key) -> ResultType;
/// release a given key formally pressed down by
/// [key_down](trait.KeyboardControllable.html#tymethod.key_down)
fn key_up(&mut self, key: Key);
/// Much like the
/// [key_down](trait.KeyboardControllable.html#tymethod.key_down) and
/// [key_up](trait.KeyboardControllable.html#tymethod.key_up)
/// function they're just invoked consecutively
fn key_click(&mut self, key: Key);
///
fn get_key_state(&mut self, key: Key) -> bool;
}
#[cfg(any(target_os = "android", target_os = "ios"))]
struct Enigo;
impl Enigo {
/// Constructs a new `Enigo` instance.
///
/// # Example
///
/// ```no_run
/// use enigo::*;
/// let mut enigo = Enigo::new();
/// ```
pub fn new() -> Self {
#[cfg(any(target_os = "android", target_os = "ios"))]
return Enigo{};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
Self::default()
}
}
use std::fmt;
impl fmt::Debug for Enigo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Enigo")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_key_state() {
let mut enigo = Enigo::new();
let keys = [Key::CapsLock, Key::NumLock];
for k in keys.iter() {
enigo.key_click(k.clone());
let a = enigo.get_key_state(k.clone());
enigo.key_click(k.clone());
let b = enigo.get_key_state(k.clone());
assert!(a != b);
}
let keys = [Key::Control, Key::Alt, Key::Shift];
for k in keys.iter() {
enigo.key_down(k.clone()).ok();
let a = enigo.get_key_state(k.clone());
enigo.key_up(k.clone());
let b = enigo.get_key_state(k.clone());
assert!(a != b);
}
}
}

361
libs/enigo/src/linux.rs Normal file
View File

@@ -0,0 +1,361 @@
use libc;
use crate::{Key, KeyboardControllable, MouseButton, MouseControllable};
use self::libc::{c_char, c_int, c_void, useconds_t};
use std::{borrow::Cow, ffi::CString, ptr};
const CURRENT_WINDOW: c_int = 0;
const DEFAULT_DELAY: u64 = 12000;
type Window = c_int;
type Xdo = *const c_void;
#[link(name = "xdo")]
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_input_state(xdo: Xdo) -> u32;
}
fn mousebutton(button: MouseButton) -> c_int {
match button {
MouseButton::Left => 1,
MouseButton::Middle => 2,
MouseButton::Right => 3,
MouseButton::ScrollUp => 4,
MouseButton::ScrollDown => 5,
MouseButton::ScrollLeft => 6,
MouseButton::ScrollRight => 7,
}
}
/// The main struct for handling the event emitting
pub struct Enigo {
xdo: Xdo,
delay: u64,
}
// This is safe, we have a unique pointer.
// TODO: use Unique<c_char> once stable.
unsafe impl Send for Enigo {}
impl Default for Enigo {
/// Create a new Enigo instance
fn default() -> Self {
Self {
xdo: unsafe { xdo_new(ptr::null()) },
delay: DEFAULT_DELAY,
}
}
}
impl Enigo {
/// Get the delay per keypress.
/// Default value is 12000.
/// This is Linux-specific.
pub fn delay(&self) -> u64 {
self.delay
}
/// Set the delay per keypress.
/// This is Linux-specific.
pub fn set_delay(&mut self, delay: u64) {
self.delay = delay;
}
}
impl Drop for Enigo {
fn drop(&mut self) {
if self.xdo.is_null() {
return;
}
unsafe {
xdo_free(self.xdo);
}
}
}
impl MouseControllable for Enigo {
fn mouse_move_to(&mut self, x: i32, y: i32) {
if self.xdo.is_null() {
return;
}
unsafe {
xdo_move_mouse(self.xdo, x as c_int, y as c_int, 0);
}
}
fn mouse_move_relative(&mut self, x: i32, y: i32) {
if self.xdo.is_null() {
return;
}
unsafe {
xdo_move_mouse_relative(self.xdo, x as c_int, y as c_int);
}
}
fn mouse_down(&mut self, button: MouseButton) -> crate::ResultType {
if self.xdo.is_null() {
return Ok(());
}
unsafe {
xdo_mouse_down(self.xdo, CURRENT_WINDOW, mousebutton(button));
}
Ok(())
}
fn mouse_up(&mut self, button: MouseButton) {
if self.xdo.is_null() {
return;
}
unsafe {
xdo_mouse_up(self.xdo, CURRENT_WINDOW, mousebutton(button));
}
}
fn mouse_click(&mut self, button: MouseButton) {
if self.xdo.is_null() {
return;
}
unsafe {
xdo_click_window(self.xdo, CURRENT_WINDOW, mousebutton(button));
}
}
fn mouse_scroll_x(&mut self, length: i32) {
let button;
let mut length = length;
if length < 0 {
button = MouseButton::ScrollLeft;
} else {
button = MouseButton::ScrollRight;
}
if length < 0 {
length = -length;
}
for _ in 0..length {
self.mouse_click(button);
}
}
fn mouse_scroll_y(&mut self, length: i32) {
let button;
let mut length = length;
if length < 0 {
button = MouseButton::ScrollUp;
} else {
button = MouseButton::ScrollDown;
}
if length < 0 {
length = -length;
}
for _ in 0..length {
self.mouse_click(button);
}
}
}
fn keysequence<'a>(key: Key) -> Cow<'a, str> {
if let Key::Layout(c) = key {
return Cow::Owned(format!("U{:X}", c as u32));
}
if let Key::Raw(k) = key {
return Cow::Owned(format!("{}", k as u16));
}
#[allow(deprecated)]
// I mean duh, we still need to support deprecated keys until they're removed
// https://www.rubydoc.info/gems/xdo/XDo/Keyboard
// https://gitlab.com/cunidev/gestures/-/wikis/xdotool-list-of-key-codes
Cow::Borrowed(match key {
Key::Alt => "Alt",
Key::Backspace => "BackSpace",
Key::CapsLock => "Caps_Lock",
Key::Control => "Control",
Key::Delete => "Delete",
Key::DownArrow => "Down",
Key::End => "End",
Key::Escape => "Escape",
Key::F1 => "F1",
Key::F10 => "F10",
Key::F11 => "F11",
Key::F12 => "F12",
Key::F2 => "F2",
Key::F3 => "F3",
Key::F4 => "F4",
Key::F5 => "F5",
Key::F6 => "F6",
Key::F7 => "F7",
Key::F8 => "F8",
Key::F9 => "F9",
Key::Home => "Home",
//Key::Layout(_) => unreachable!(),
Key::LeftArrow => "Left",
Key::Option => "Option",
Key::PageDown => "Page_Down",
Key::PageUp => "Page_Up",
//Key::Raw(_) => unreachable!(),
Key::Return => "Return",
Key::RightArrow => "Right",
Key::Shift => "Shift",
Key::Space => "space",
Key::Tab => "Tab",
Key::UpArrow => "Up",
Key::Numpad0 => "U30", //"KP_0",
Key::Numpad1 => "U31", //"KP_1",
Key::Numpad2 => "U32", //"KP_2",
Key::Numpad3 => "U33", //"KP_3",
Key::Numpad4 => "U34", //"KP_4",
Key::Numpad5 => "U35", //"KP_5",
Key::Numpad6 => "U36", //"KP_6",
Key::Numpad7 => "U37", //"KP_7",
Key::Numpad8 => "U38", //"KP_8",
Key::Numpad9 => "U39", //"KP_9",
Key::Decimal => "U2E", //"KP_Decimal",
Key::Cancel => "Cancel",
Key::Clear => "Clear",
Key::Menu => "Menu",
Key::Pause => "Pause",
Key::Kana => "Kana",
Key::Hangul => "Hangul",
Key::Junja => "",
Key::Final => "",
Key::Hanja => "Hanja",
Key::Kanji => "Kanji",
Key::Convert => "",
Key::Select => "Select",
Key::Print => "Print",
Key::Execute => "Execute",
Key::Snapshot => "3270_PrintScreen",
Key::Insert => "Insert",
Key::Help => "Help",
Key::Sleep => "",
Key::Separator => "KP_Separator",
Key::VolumeUp => "",
Key::VolumeDown => "",
Key::Mute => "",
Key::Scroll => "Scroll_Lock",
Key::NumLock => "Num_Lock",
Key::RWin => "",
Key::Apps => "",
Key::Multiply => "KP_Multiply",
Key::Add => "KP_Add",
Key::Subtract => "KP_Subtract",
Key::Divide => "KP_Divide",
Key::Equals => "KP_Equal",
Key::NumpadEnter => "KP_Enter",
Key::Command | Key::Super | Key::Windows | Key::Meta => "Super",
_ => "",
})
}
impl KeyboardControllable for Enigo {
fn get_key_state(&mut self, key: Key) -> bool {
if self.xdo.is_null() {
return false;
}
let mod_shift = 1 << 0;
let mod_lock = 1 << 1;
let mod_control = 1 << 2;
let mod_alt = 1 << 3;
let mod_numlock = 1 << 4;
let mod_meta = 1 << 6;
let mask = unsafe { xdo_get_input_state(self.xdo) };
// println!("{:b}", mask);
match key {
Key::Shift => mask & mod_shift != 0,
Key::CapsLock => mask & mod_lock != 0,
Key::Control => mask & mod_control != 0,
Key::Alt => mask & mod_alt != 0,
Key::NumLock => mask & mod_numlock != 0,
Key::Meta => mask & mod_meta != 0,
_ => false,
}
}
fn key_sequence(&mut self, sequence: &str) {
if self.xdo.is_null() {
return;
}
if let Ok(string) = CString::new(sequence) {
unsafe {
xdo_enter_text_window(
self.xdo,
CURRENT_WINDOW,
string.as_ptr(),
self.delay as useconds_t,
);
}
}
}
fn key_down(&mut self, key: Key) -> crate::ResultType {
if self.xdo.is_null() {
return Ok(());
}
let string = CString::new(&*keysequence(key))?;
unsafe {
xdo_send_keysequence_window_down(
self.xdo,
CURRENT_WINDOW,
string.as_ptr(),
self.delay as useconds_t,
);
}
Ok(())
}
fn key_up(&mut self, key: Key) {
if self.xdo.is_null() {
return;
}
if let Ok(string) = CString::new(&*keysequence(key)) {
unsafe {
xdo_send_keysequence_window_up(
self.xdo,
CURRENT_WINDOW,
string.as_ptr(),
self.delay as useconds_t,
);
}
}
}
fn key_click(&mut self, key: Key) {
if self.xdo.is_null() {
return;
}
if let Ok(string) = CString::new(&*keysequence(key)) {
unsafe {
xdo_send_keysequence_window(
self.xdo,
CURRENT_WINDOW,
string.as_ptr(),
self.delay as useconds_t,
);
}
}
}
}

View File

@@ -0,0 +1,72 @@
// https://stackoverflow.com/questions/3202629/where-can-i-find-a-list-of-mac-virtual-key-codes
/* keycodes for keys that are independent of keyboard layout */
#![allow(non_upper_case_globals)]
#![allow(dead_code)]
pub const kVK_Return: u16 = 0x24;
pub const kVK_Tab: u16 = 0x30;
pub const kVK_Space: u16 = 0x31;
pub const kVK_Delete: u16 = 0x33;
pub const kVK_Escape: u16 = 0x35;
pub const kVK_Command: u16 = 0x37;
pub const kVK_Shift: u16 = 0x38;
pub const kVK_CapsLock: u16 = 0x39;
pub const kVK_Option: u16 = 0x3A;
pub const kVK_Control: u16 = 0x3B;
pub const kVK_RightShift: u16 = 0x3C;
pub const kVK_RightOption: u16 = 0x3D;
pub const kVK_RightControl: u16 = 0x3E;
pub const kVK_Function: u16 = 0x3F;
pub const kVK_F17: u16 = 0x40;
pub const kVK_VolumeUp: u16 = 0x48;
pub const kVK_VolumeDown: u16 = 0x49;
pub const kVK_Mute: u16 = 0x4A;
pub const kVK_F18: u16 = 0x4F;
pub const kVK_F19: u16 = 0x50;
pub const kVK_F20: u16 = 0x5A;
pub const kVK_F5: u16 = 0x60;
pub const kVK_F6: u16 = 0x61;
pub const kVK_F7: u16 = 0x62;
pub const kVK_F3: u16 = 0x63;
pub const kVK_F8: u16 = 0x64;
pub const kVK_F9: u16 = 0x65;
pub const kVK_F11: u16 = 0x67;
pub const kVK_F13: u16 = 0x69;
pub const kVK_F16: u16 = 0x6A;
pub const kVK_F14: u16 = 0x6B;
pub const kVK_F10: u16 = 0x6D;
pub const kVK_F12: u16 = 0x6F;
pub const kVK_F15: u16 = 0x71;
pub const kVK_Help: u16 = 0x72;
pub const kVK_Home: u16 = 0x73;
pub const kVK_PageUp: u16 = 0x74;
pub const kVK_ForwardDelete: u16 = 0x75;
pub const kVK_F4: u16 = 0x76;
pub const kVK_End: u16 = 0x77;
pub const kVK_F2: u16 = 0x78;
pub const kVK_PageDown: u16 = 0x79;
pub const kVK_F1: u16 = 0x7A;
pub const kVK_LeftArrow: u16 = 0x7B;
pub const kVK_RightArrow: u16 = 0x7C;
pub const kVK_DownArrow: u16 = 0x7D;
pub const kVK_UpArrow: u16 = 0x7E;
pub const kVK_ANSI_Keypad0: u16 = 0x52;
pub const kVK_ANSI_Keypad1: u16 = 0x53;
pub const kVK_ANSI_Keypad2: u16 = 0x54;
pub const kVK_ANSI_Keypad3: u16 = 0x55;
pub const kVK_ANSI_Keypad4: u16 = 0x56;
pub const kVK_ANSI_Keypad5: u16 = 0x57;
pub const kVK_ANSI_Keypad6: u16 = 0x58;
pub const kVK_ANSI_Keypad7: u16 = 0x59;
pub const kVK_ANSI_Keypad8: u16 = 0x5B;
pub const kVK_ANSI_Keypad9: u16 = 0x5C;
pub const kVK_ANSI_KeypadClear: u16 = 0x47;
pub const kVK_ANSI_KeypadDecimal: u16 = 0x41;
pub const kVK_ANSI_KeypadMultiply: u16 = 0x43;
pub const kVK_ANSI_KeypadPlus: u16 = 0x45;
pub const kVK_ANSI_KeypadDivide: u16 = 0x4B;
pub const kVK_ANSI_KeypadEnter: u16 = 0x4C;
pub const kVK_ANSI_KeypadMinus: u16 = 0x4E;
pub const kVK_ANSI_KeypadEquals: u16 = 0x51;

View File

@@ -0,0 +1,680 @@
use core_graphics;
// TODO(dustin): use only the things i need
use self::core_graphics::display::*;
use self::core_graphics::event::*;
use self::core_graphics::event_source::*;
use crate::macos::keycodes::*;
use crate::{Key, KeyboardControllable, MouseButton, MouseControllable};
use objc::runtime::Class;
use std::ffi::CStr;
use std::os::raw::*;
// required for pressedMouseButtons on NSEvent
#[link(name = "AppKit", kind = "framework")]
extern "C" {}
struct MyCGEvent;
#[allow(improper_ctypes)]
#[allow(non_snake_case)]
#[link(name = "ApplicationServices", kind = "framework")]
extern "C" {
fn CGEventPost(tapLocation: CGEventTapLocation, event: *mut MyCGEvent);
// not present in servo/core-graphics
fn CGEventCreateScrollWheelEvent(
source: &CGEventSourceRef,
units: ScrollUnit,
wheelCount: u32,
wheel1: i32,
...
) -> *mut MyCGEvent;
fn CGEventSourceKeyState(stateID: i32, key: u16) -> bool;
}
pub type CFDataRef = *const c_void;
#[repr(C)]
#[derive(Clone, Copy)]
struct NSPoint {
x: f64,
y: f64,
}
#[repr(C)]
pub struct __TISInputSource;
pub type TISInputSourceRef = *const __TISInputSource;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct __CFString([u8; 0]);
pub type CFStringRef = *const __CFString;
pub type Boolean = c_uchar;
pub type UInt8 = c_uchar;
pub type SInt32 = c_int;
pub type UInt16 = c_ushort;
pub type UInt32 = c_uint;
pub type UniChar = UInt16;
pub type UniCharCount = c_ulong;
pub type OptionBits = UInt32;
pub type OSStatus = SInt32;
pub type CFStringEncoding = UInt32;
#[allow(non_upper_case_globals)]
pub const kUCKeyActionDisplay: _bindgen_ty_702 = _bindgen_ty_702::kUCKeyActionDisplay;
#[allow(non_camel_case_types)]
#[repr(u32)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum _bindgen_ty_702 {
// kUCKeyActionDown = 0,
// kUCKeyActionUp = 1,
// kUCKeyActionAutoKey = 2,
kUCKeyActionDisplay = 3,
}
#[allow(non_snake_case)]
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct UCKeyboardTypeHeader {
pub keyboardTypeFirst: UInt32,
pub keyboardTypeLast: UInt32,
pub keyModifiersToTableNumOffset: UInt32,
pub keyToCharTableIndexOffset: UInt32,
pub keyStateRecordsIndexOffset: UInt32,
pub keyStateTerminatorsOffset: UInt32,
pub keySequenceDataIndexOffset: UInt32,
}
#[allow(non_snake_case)]
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct UCKeyboardLayout {
pub keyLayoutHeaderFormat: UInt16,
pub keyLayoutDataVersion: UInt16,
pub keyLayoutFeatureInfoOffset: UInt32,
pub keyboardTypeCount: UInt32,
pub keyboardTypeList: [UCKeyboardTypeHeader; 1usize],
}
#[allow(non_upper_case_globals)]
pub const kUCKeyTranslateNoDeadKeysBit: _bindgen_ty_703 =
_bindgen_ty_703::kUCKeyTranslateNoDeadKeysBit;
#[allow(non_camel_case_types)]
#[repr(u32)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum _bindgen_ty_703 {
kUCKeyTranslateNoDeadKeysBit = 0,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct __CFAllocator([u8; 0]);
pub type CFAllocatorRef = *const __CFAllocator;
// #[repr(u32)]
// #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
// pub enum _bindgen_ty_15 {
// kCFStringEncodingMacRoman = 0,
// kCFStringEncodingWindowsLatin1 = 1280,
// kCFStringEncodingISOLatin1 = 513,
// kCFStringEncodingNextStepLatin = 2817,
// kCFStringEncodingASCII = 1536,
// kCFStringEncodingUnicode = 256,
// kCFStringEncodingUTF8 = 134217984,
// kCFStringEncodingNonLossyASCII = 3071,
// kCFStringEncodingUTF16BE = 268435712,
// kCFStringEncodingUTF16LE = 335544576,
// kCFStringEncodingUTF32 = 201326848,
// kCFStringEncodingUTF32BE = 402653440,
// kCFStringEncodingUTF32LE = 469762304,
// }
#[allow(non_upper_case_globals)]
pub const kCFStringEncodingUTF8: u32 = 134_217_984;
#[allow(improper_ctypes)]
#[link(name = "Carbon", kind = "framework")]
extern "C" {
fn TISCopyCurrentKeyboardInputSource() -> TISInputSourceRef;
// extern void *
// TISGetInputSourceProperty(
// TISInputSourceRef inputSource,
// CFStringRef propertyKey)
#[allow(non_upper_case_globals)]
#[link_name = "kTISPropertyUnicodeKeyLayoutData"]
pub static kTISPropertyUnicodeKeyLayoutData: CFStringRef;
#[allow(non_snake_case)]
pub fn TISGetInputSourceProperty(
inputSource: TISInputSourceRef,
propertyKey: CFStringRef,
) -> *mut c_void;
#[allow(non_snake_case)]
pub fn CFDataGetBytePtr(theData: CFDataRef) -> *const UInt8;
#[allow(non_snake_case)]
pub fn UCKeyTranslate(
keyLayoutPtr: *const UInt8, //*const UCKeyboardLayout,
virtualKeyCode: UInt16,
keyAction: UInt16,
modifierKeyState: UInt32,
keyboardType: UInt32,
keyTranslateOptions: OptionBits,
deadKeyState: *mut UInt32,
maxStringLength: UniCharCount,
actualStringLength: *mut UniCharCount,
unicodeString: *mut UniChar,
) -> OSStatus;
pub fn LMGetKbdType() -> UInt8;
#[allow(non_snake_case)]
pub fn CFStringCreateWithCharacters(
alloc: CFAllocatorRef,
chars: *const UniChar,
numChars: CFIndex,
) -> CFStringRef;
#[allow(non_upper_case_globals)]
#[link_name = "kCFAllocatorDefault"]
pub static kCFAllocatorDefault: CFAllocatorRef;
#[allow(non_snake_case)]
pub fn CFStringGetCString(
theString: CFStringRef,
buffer: *mut c_char,
bufferSize: CFIndex,
encoding: CFStringEncoding,
) -> Boolean;
}
// not present in servo/core-graphics
#[allow(dead_code)]
#[derive(Debug)]
enum ScrollUnit {
Pixel = 0,
Line = 1,
}
// hack
/// The main struct for handling the event emitting
pub struct Enigo {
event_source: Option<CGEventSource>,
keycode_to_string_map: std::collections::HashMap<String, CGKeyCode>,
double_click_interval: u32,
last_click_time: Option<std::time::Instant>,
multiple_click: i64,
flags: CGEventFlags,
}
impl Enigo {
///
pub fn reset_flag(&mut self) {
self.flags = CGEventFlags::CGEventFlagNull;
}
///
pub fn add_flag(&mut self, key: &Key) {
let flag = match key {
&Key::CapsLock => CGEventFlags::CGEventFlagAlphaShift,
&Key::Shift => CGEventFlags::CGEventFlagShift,
&Key::Control => CGEventFlags::CGEventFlagControl,
&Key::Alt => CGEventFlags::CGEventFlagAlternate,
&Key::Meta => CGEventFlags::CGEventFlagCommand,
&Key::NumLock => CGEventFlags::CGEventFlagNumericPad,
_ => CGEventFlags::CGEventFlagNull,
};
self.flags |= flag;
}
fn post(&self, event: CGEvent) {
event.set_flags(self.flags);
event.post(CGEventTapLocation::HID);
}
}
impl Default for Enigo {
fn default() -> Self {
let mut double_click_interval = 500;
if let Some(ns_event) = Class::get("NSEvent") {
let tm: f64 = unsafe { msg_send![ns_event, doubleClickInterval] };
if tm > 0. {
double_click_interval = (tm * 1000.) as u32;
log::info!("double click interval: {}ms", double_click_interval);
}
}
Self {
// TODO(dustin): return error rather than panic here
event_source: if let Ok(src) =
CGEventSource::new(CGEventSourceStateID::CombinedSessionState)
{
Some(src)
} else {
None
},
keycode_to_string_map: Default::default(),
double_click_interval,
multiple_click: 1,
last_click_time: None,
flags: CGEventFlags::CGEventFlagNull,
}
}
}
impl MouseControllable for Enigo {
fn mouse_move_to(&mut self, x: i32, y: i32) {
let pressed = Self::pressed_buttons();
let event_type = if pressed & 1 > 0 {
CGEventType::LeftMouseDragged
} else if pressed & 2 > 0 {
CGEventType::RightMouseDragged
} else {
CGEventType::MouseMoved
};
let dest = CGPoint::new(x as f64, y as f64);
if let Some(src) = self.event_source.as_ref() {
if let Ok(event) =
CGEvent::new_mouse_event(src.clone(), event_type, dest, CGMouseButton::Left)
{
self.post(event);
}
}
}
fn mouse_move_relative(&mut self, x: i32, y: i32) {
let (display_width, display_height) = Self::main_display_size();
let (current_x, y_inv) = Self::mouse_location_raw_coords();
let current_y = (display_height as i32) - y_inv;
let new_x = current_x + x;
let new_y = current_y + y;
if new_x < 0
|| new_x as usize > display_width
|| new_y < 0
|| new_y as usize > display_height
{
return;
}
self.mouse_move_to(new_x, new_y);
}
fn mouse_down(&mut self, button: MouseButton) -> crate::ResultType {
let now = std::time::Instant::now();
if let Some(t) = self.last_click_time {
if t.elapsed().as_millis() as u32 <= self.double_click_interval {
self.multiple_click += 1;
} else {
self.multiple_click = 1;
}
}
self.last_click_time = Some(now);
let (current_x, current_y) = Self::mouse_location();
let (button, event_type) = match button {
MouseButton::Left => (CGMouseButton::Left, CGEventType::LeftMouseDown),
MouseButton::Middle => (CGMouseButton::Center, CGEventType::OtherMouseDown),
MouseButton::Right => (CGMouseButton::Right, CGEventType::RightMouseDown),
_ => unimplemented!(),
};
let dest = CGPoint::new(current_x as f64, current_y as f64);
if let Some(src) = self.event_source.as_ref() {
if let Ok(event) = CGEvent::new_mouse_event(src.clone(), event_type, dest, button) {
if self.multiple_click > 1 {
event.set_integer_value_field(
EventField::MOUSE_EVENT_CLICK_STATE,
self.multiple_click,
);
}
self.post(event);
}
}
Ok(())
}
fn mouse_up(&mut self, button: MouseButton) {
let (current_x, current_y) = Self::mouse_location();
let (button, event_type) = match button {
MouseButton::Left => (CGMouseButton::Left, CGEventType::LeftMouseUp),
MouseButton::Middle => (CGMouseButton::Center, CGEventType::OtherMouseUp),
MouseButton::Right => (CGMouseButton::Right, CGEventType::RightMouseUp),
_ => unimplemented!(),
};
let dest = CGPoint::new(current_x as f64, current_y as f64);
if let Some(src) = self.event_source.as_ref() {
if let Ok(event) = CGEvent::new_mouse_event(src.clone(), event_type, dest, button) {
if self.multiple_click > 1 {
event.set_integer_value_field(
EventField::MOUSE_EVENT_CLICK_STATE,
self.multiple_click,
);
}
self.post(event);
}
}
}
fn mouse_click(&mut self, button: MouseButton) {
self.mouse_down(button).ok();
self.mouse_up(button);
}
fn mouse_scroll_x(&mut self, length: i32) {
let mut scroll_direction = -1; // 1 left -1 right;
let mut length = length;
if length < 0 {
length *= -1;
scroll_direction *= -1;
}
if let Some(src) = self.event_source.as_ref() {
for _ in 0..length {
unsafe {
let mouse_ev = CGEventCreateScrollWheelEvent(
&src,
ScrollUnit::Line,
2, // CGWheelCount 1 = y 2 = xy 3 = xyz
0,
scroll_direction,
);
CGEventPost(CGEventTapLocation::HID, mouse_ev);
CFRelease(mouse_ev as *const std::ffi::c_void);
}
}
}
}
fn mouse_scroll_y(&mut self, length: i32) {
let mut scroll_direction = -1; // 1 left -1 right;
let mut length = length;
if length < 0 {
length *= -1;
scroll_direction *= -1;
}
if let Some(src) = self.event_source.as_ref() {
for _ in 0..length {
unsafe {
let mouse_ev = CGEventCreateScrollWheelEvent(
&src,
ScrollUnit::Line,
1, // CGWheelCount 1 = y 2 = xy 3 = xyz
scroll_direction,
);
CGEventPost(CGEventTapLocation::HID, mouse_ev);
CFRelease(mouse_ev as *const std::ffi::c_void);
}
}
}
}
}
// https://stackoverflow.
// com/questions/1918841/how-to-convert-ascii-character-to-cgkeycode
impl KeyboardControllable for Enigo {
fn key_sequence(&mut self, sequence: &str) {
// NOTE(dustin): This is a fix for issue https://github.com/enigo-rs/enigo/issues/68
// TODO(dustin): This could be improved by aggregating 20 bytes worth of graphemes at a time
// but i am unsure what would happen for grapheme clusters greater than 20 bytes ...
use unicode_segmentation::UnicodeSegmentation;
let clusters = UnicodeSegmentation::graphemes(sequence, true).collect::<Vec<&str>>();
for cluster in clusters {
if let Some(src) = self.event_source.as_ref() {
if let Ok(event) = CGEvent::new_keyboard_event(src.clone(), 0, true) {
event.set_string(cluster);
self.post(event);
}
}
}
}
fn key_click(&mut self, key: Key) {
let keycode = self.key_to_keycode(key);
if keycode == 0 {
return;
}
if let Some(src) = self.event_source.as_ref() {
if let Ok(event) = CGEvent::new_keyboard_event(src.clone(), keycode, true) {
self.post(event);
}
if let Ok(event) = CGEvent::new_keyboard_event(src.clone(), keycode, false) {
self.post(event);
}
}
}
fn key_down(&mut self, key: Key) -> crate::ResultType {
if let Some(src) = self.event_source.as_ref() {
if let Ok(event) =
CGEvent::new_keyboard_event(src.clone(), self.key_to_keycode(key), true)
{
self.post(event);
}
}
Ok(())
}
fn key_up(&mut self, key: Key) {
if let Some(src) = self.event_source.as_ref() {
if let Ok(event) =
CGEvent::new_keyboard_event(src.clone(), self.key_to_keycode(key), false)
{
self.post(event);
}
}
}
fn get_key_state(&mut self, key: Key) -> bool {
let keycode = self.key_to_keycode(key);
unsafe { CGEventSourceKeyState(1, keycode) }
}
}
impl Enigo {
fn pressed_buttons() -> usize {
if let Some(ns_event) = Class::get("NSEvent") {
unsafe { msg_send![ns_event, pressedMouseButtons] }
} else {
0
}
}
/// Fetches the `(width, height)` in pixels of the main display
pub fn main_display_size() -> (usize, usize) {
let display_id = unsafe { CGMainDisplayID() };
let width = unsafe { CGDisplayPixelsWide(display_id) };
let height = unsafe { CGDisplayPixelsHigh(display_id) };
(width, height)
}
/// Returns the current mouse location in Cocoa coordinates which have Y
/// inverted from the Carbon coordinates used in the rest of the API.
/// This function exists so that mouse_move_relative only has to fetch
/// the screen size once.
fn mouse_location_raw_coords() -> (i32, i32) {
if let Some(ns_event) = Class::get("NSEvent") {
let pt: NSPoint = unsafe { msg_send![ns_event, mouseLocation] };
(pt.x as i32, pt.y as i32)
} else {
(0, 0)
}
}
/// The mouse coordinates in points, only works on the main display
pub fn mouse_location() -> (i32, i32) {
let (x, y_inv) = Self::mouse_location_raw_coords();
let (_, display_height) = Self::main_display_size();
(x, (display_height as i32) - y_inv)
}
fn key_to_keycode(&mut self, key: Key) -> CGKeyCode {
#[allow(deprecated)]
// I mean duh, we still need to support deprecated keys until they're removed
match key {
Key::Alt => kVK_Option,
Key::Backspace => kVK_Delete,
Key::CapsLock => kVK_CapsLock,
Key::Control => kVK_Control,
Key::Delete => kVK_ForwardDelete,
Key::DownArrow => kVK_DownArrow,
Key::End => kVK_End,
Key::Escape => kVK_Escape,
Key::F1 => kVK_F1,
Key::F10 => kVK_F10,
Key::F11 => kVK_F11,
Key::F12 => kVK_F12,
Key::F2 => kVK_F2,
Key::F3 => kVK_F3,
Key::F4 => kVK_F4,
Key::F5 => kVK_F5,
Key::F6 => kVK_F6,
Key::F7 => kVK_F7,
Key::F8 => kVK_F8,
Key::F9 => kVK_F9,
Key::Home => kVK_Home,
Key::LeftArrow => kVK_LeftArrow,
Key::Option => kVK_Option,
Key::PageDown => kVK_PageDown,
Key::PageUp => kVK_PageUp,
Key::Return => kVK_Return,
Key::RightArrow => kVK_RightArrow,
Key::Shift => kVK_Shift,
Key::Space => kVK_Space,
Key::Tab => kVK_Tab,
Key::UpArrow => kVK_UpArrow,
Key::Numpad0 => kVK_ANSI_Keypad0,
Key::Numpad1 => kVK_ANSI_Keypad1,
Key::Numpad2 => kVK_ANSI_Keypad2,
Key::Numpad3 => kVK_ANSI_Keypad3,
Key::Numpad4 => kVK_ANSI_Keypad4,
Key::Numpad5 => kVK_ANSI_Keypad5,
Key::Numpad6 => kVK_ANSI_Keypad6,
Key::Numpad7 => kVK_ANSI_Keypad7,
Key::Numpad8 => kVK_ANSI_Keypad8,
Key::Numpad9 => kVK_ANSI_Keypad9,
Key::Mute => kVK_Mute,
Key::VolumeDown => kVK_VolumeUp,
Key::VolumeUp => kVK_VolumeDown,
Key::Help => kVK_Help,
Key::Snapshot => kVK_F13,
Key::Clear => kVK_ANSI_KeypadClear,
Key::Decimal => kVK_ANSI_KeypadDecimal,
Key::Multiply => kVK_ANSI_KeypadMultiply,
Key::Add => kVK_ANSI_KeypadPlus,
Key::Divide => kVK_ANSI_KeypadDivide,
Key::NumpadEnter => kVK_ANSI_KeypadEnter,
Key::Subtract => kVK_ANSI_KeypadMinus,
Key::Equals => kVK_ANSI_KeypadEquals,
Key::NumLock => kVK_ANSI_KeypadClear,
Key::Raw(raw_keycode) => raw_keycode,
Key::Layout(c) => self.get_layoutdependent_keycode(c.to_string()),
Key::Super | Key::Command | Key::Windows | Key::Meta => kVK_Command,
_ => 0,
}
}
fn get_layoutdependent_keycode(&mut self, string: String) -> CGKeyCode {
if self.keycode_to_string_map.is_empty() {
self.init_map();
}
*self.keycode_to_string_map.get(&string).unwrap_or(&0)
}
fn init_map(&mut self) {
self.keycode_to_string_map.insert("".to_owned(), 0);
// loop through every keycode (0 - 127)
for keycode in 0..128 {
// no modifier
if let Some(key_string) = self.keycode_to_string(keycode, 0x100) {
self.keycode_to_string_map.insert(key_string, keycode);
}
// shift modifier
if let Some(key_string) = self.keycode_to_string(keycode, 0x20102) {
self.keycode_to_string_map.insert(key_string, keycode);
}
// alt modifier
// if let Some(string) = self.keycode_to_string(keycode, 0x80120) {
// println!("{:?}", string);
// }
// alt + shift modifier
// if let Some(string) = self.keycode_to_string(keycode, 0xa0122) {
// println!("{:?}", string);
// }
}
}
fn keycode_to_string(&self, keycode: u16, modifier: u32) -> Option<String> {
let cf_string = self.create_string_for_key(keycode, modifier);
unsafe {
if !cf_string.is_null() {
let mut buf: [i8; 255] = [0; 255];
let success = CFStringGetCString(
cf_string,
buf.as_mut_ptr(),
buf.len() as _,
kCFStringEncodingUTF8,
);
if success != 0 {
let name: &CStr = CStr::from_ptr(buf.as_ptr());
if let Ok(name) = name.to_str() {
return Some(name.to_owned());
}
}
}
}
None
}
fn create_string_for_key(&self, keycode: u16, modifier: u32) -> CFStringRef {
let current_keyboard = unsafe { TISCopyCurrentKeyboardInputSource() };
let layout_data = unsafe {
TISGetInputSourceProperty(current_keyboard, kTISPropertyUnicodeKeyLayoutData)
};
let keyboard_layout = unsafe { CFDataGetBytePtr(layout_data) };
let mut keys_down: UInt32 = 0;
// let mut chars: *mut c_void;//[UniChar; 4];
let mut chars: u16 = 0;
let mut real_length: UniCharCount = 0;
unsafe {
UCKeyTranslate(
keyboard_layout,
keycode,
kUCKeyActionDisplay as u16,
modifier,
LMGetKbdType() as u32,
kUCKeyTranslateNoDeadKeysBit as u32,
&mut keys_down,
8, // sizeof(chars) / sizeof(chars[0]),
&mut real_length,
&mut chars,
);
}
unsafe { CFStringCreateWithCharacters(kCFAllocatorDefault, &chars, 1) }
}
}
unsafe impl Send for Enigo {}

View File

@@ -0,0 +1,4 @@
mod macos_impl;
pub mod keycodes;
pub use self::macos_impl::Enigo;

View File

@@ -0,0 +1,73 @@
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731
pub const EVK_RETURN: u16 = 0x0D;
pub const EVK_TAB: u16 = 0x09;
pub const EVK_SPACE: u16 = 0x20;
pub const EVK_BACK: u16 = 0x08;
pub const EVK_ESCAPE: u16 = 0x1b;
pub const EVK_LWIN: u16 = 0x5b;
pub const EVK_SHIFT: u16 = 0x10;
pub const EVK_CAPITAL: u16 = 0x14;
pub const EVK_MENU: u16 = 0x12;
pub const EVK_LCONTROL: u16 = 0xa2;
pub const EVK_HOME: u16 = 0x24;
pub const EVK_PRIOR: u16 = 0x21;
pub const EVK_NEXT: u16 = 0x22;
pub const EVK_END: u16 = 0x23;
pub const EVK_LEFT: u16 = 0x25;
pub const EVK_RIGHT: u16 = 0x27;
pub const EVK_UP: u16 = 0x26;
pub const EVK_DOWN: u16 = 0x28;
pub const EVK_DELETE: u16 = 0x2E;
pub const EVK_F1: u16 = 0x70;
pub const EVK_F2: u16 = 0x71;
pub const EVK_F3: u16 = 0x72;
pub const EVK_F4: u16 = 0x73;
pub const EVK_F5: u16 = 0x74;
pub const EVK_F6: u16 = 0x75;
pub const EVK_F7: u16 = 0x76;
pub const EVK_F8: u16 = 0x77;
pub const EVK_F9: u16 = 0x78;
pub const EVK_F10: u16 = 0x79;
pub const EVK_F11: u16 = 0x7a;
pub const EVK_F12: u16 = 0x7b;
pub const EVK_NUMPAD0: u16 = 0x60;
pub const EVK_NUMPAD1: u16 = 0x61;
pub const EVK_NUMPAD2: u16 = 0x62;
pub const EVK_NUMPAD3: u16 = 0x63;
pub const EVK_NUMPAD4: u16 = 0x64;
pub const EVK_NUMPAD5: u16 = 0x65;
pub const EVK_NUMPAD6: u16 = 0x66;
pub const EVK_NUMPAD7: u16 = 0x67;
pub const EVK_NUMPAD8: u16 = 0x68;
pub const EVK_NUMPAD9: u16 = 0x69;
pub const EVK_CANCEL: u16 = 0x03;
pub const EVK_CLEAR: u16 = 0x0C;
pub const EVK_PAUSE: u16 = 0x13;
pub const EVK_KANA: u16 = 0x15;
pub const EVK_HANGUL: u16 = 0x15;
pub const EVK_JUNJA: u16 = 0x17;
pub const EVK_FINAL: u16 = 0x18;
pub const EVK_HANJA: u16 = 0x19;
pub const EVK_KANJI: u16 = 0x19;
pub const EVK_CONVERT: u16 = 0x1C;
pub const EVK_SELECT: u16 = 0x29;
pub const EVK_PRINT: u16 = 0x2A;
pub const EVK_EXECUTE: u16 = 0x2B;
pub const EVK_SNAPSHOT: u16 = 0x2C;
pub const EVK_INSERT: u16 = 0x2D;
pub const EVK_HELP: u16 = 0x2F;
pub const EVK_SLEEP: u16 = 0x5F;
pub const EVK_SEPARATOR: u16 = 0x6C;
pub const EVK_VOLUME_MUTE: u16 = 0xAD;
pub const EVK_VOLUME_DOWN: u16 = 0xAE;
pub const EVK_VOLUME_UP: u16 = 0xAF;
pub const EVK_NUMLOCK: u16 = 0x90;
pub const EVK_SCROLL: u16 = 0x91;
pub const EVK_RWIN: u16 = 0x5C;
pub const EVK_APPS: u16 = 0x5D;
pub const EVK_ADD: u16 = 0x6B;
pub const EVK_MULTIPLY: u16 = 0x6A;
pub const EVK_SUBTRACT: u16 = 0x6D;
pub const EVK_DECIMAL: u16 = 0x6E;
pub const EVK_DIVIDE: u16 = 0x6F;

View File

@@ -0,0 +1,4 @@
mod win_impl;
pub mod keycodes;
pub use self::win_impl::Enigo;

View File

@@ -0,0 +1,366 @@
use winapi;
use self::winapi::ctypes::c_int;
use self::winapi::shared::{minwindef::*, windef::*};
use self::winapi::um::winbase::*;
use self::winapi::um::winuser::*;
use crate::win::keycodes::*;
use crate::{Key, KeyboardControllable, MouseButton, MouseControllable};
use std::mem::*;
extern "system" {
pub fn GetLastError() -> DWORD;
}
/// The main struct for handling the event emitting
#[derive(Default)]
pub struct Enigo;
fn mouse_event(flags: u32, data: u32, dx: i32, dy: i32) -> DWORD {
let mut input = INPUT {
type_: INPUT_MOUSE,
u: unsafe {
transmute(MOUSEINPUT {
dx,
dy,
mouseData: data,
dwFlags: flags,
time: 0,
dwExtraInfo: 0,
})
},
};
unsafe { SendInput(1, &mut input as LPINPUT, size_of::<INPUT>() as c_int) }
}
fn keybd_event(flags: u32, vk: u16, scan: u16) -> DWORD {
let mut input = INPUT {
type_: INPUT_KEYBOARD,
u: unsafe {
transmute_copy(&KEYBDINPUT {
wVk: vk,
wScan: scan,
dwFlags: flags,
time: 0,
dwExtraInfo: 0,
})
},
};
unsafe { SendInput(1, &mut input as LPINPUT, size_of::<INPUT>() as c_int) }
}
fn get_error() -> String {
unsafe {
let buff_size = 256;
let mut buff: Vec<u16> = Vec::with_capacity(buff_size);
buff.resize(buff_size, 0);
let errno = GetLastError();
let chars_copied = FormatMessageW(
FORMAT_MESSAGE_IGNORE_INSERTS
| FORMAT_MESSAGE_FROM_SYSTEM
| FORMAT_MESSAGE_ARGUMENT_ARRAY,
std::ptr::null(),
errno,
0,
buff.as_mut_ptr(),
(buff_size + 1) as u32,
std::ptr::null_mut(),
);
if chars_copied == 0 {
return "".to_owned();
}
let mut curr_char: usize = chars_copied as usize;
while curr_char > 0 {
let ch = buff[curr_char];
if ch >= ' ' as u16 {
break;
}
curr_char -= 1;
}
let sl = std::slice::from_raw_parts(buff.as_ptr(), curr_char);
let err_msg = String::from_utf16(sl);
return err_msg.unwrap_or("".to_owned());
}
}
impl MouseControllable for Enigo {
fn mouse_move_to(&mut self, x: i32, y: i32) {
mouse_event(
MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK,
0,
(x - unsafe { GetSystemMetrics(SM_XVIRTUALSCREEN) }) * 65535
/ unsafe { GetSystemMetrics(SM_CXVIRTUALSCREEN) },
(y - unsafe { GetSystemMetrics(SM_YVIRTUALSCREEN) }) * 65535
/ unsafe { GetSystemMetrics(SM_CYVIRTUALSCREEN) },
);
}
fn mouse_move_relative(&mut self, x: i32, y: i32) {
mouse_event(MOUSEEVENTF_MOVE, 0, x, y);
}
fn mouse_down(&mut self, button: MouseButton) -> crate::ResultType {
let res = mouse_event(
match button {
MouseButton::Left => MOUSEEVENTF_LEFTDOWN,
MouseButton::Middle => MOUSEEVENTF_MIDDLEDOWN,
MouseButton::Right => MOUSEEVENTF_RIGHTDOWN,
_ => unimplemented!(),
},
0,
0,
0,
);
if res == 0 {
let err = get_error();
if !err.is_empty() {
return Err(err.into());
}
}
Ok(())
}
fn mouse_up(&mut self, button: MouseButton) {
mouse_event(
match button {
MouseButton::Left => MOUSEEVENTF_LEFTUP,
MouseButton::Middle => MOUSEEVENTF_MIDDLEUP,
MouseButton::Right => MOUSEEVENTF_RIGHTUP,
_ => unimplemented!(),
},
0,
0,
0,
);
}
fn mouse_click(&mut self, button: MouseButton) {
self.mouse_down(button).ok();
self.mouse_up(button);
}
fn mouse_scroll_x(&mut self, length: i32) {
mouse_event(MOUSEEVENTF_HWHEEL, unsafe { transmute(length * 120) }, 0, 0);
}
fn mouse_scroll_y(&mut self, length: i32) {
mouse_event(MOUSEEVENTF_WHEEL, unsafe { transmute(length * 120) }, 0, 0);
}
}
impl KeyboardControllable for Enigo {
fn key_sequence(&mut self, sequence: &str) {
let mut buffer = [0; 2];
for c in sequence.chars() {
// Windows uses uft-16 encoding. We need to check
// for variable length characters. As such some
// characters can be 32 bit long and those are
// encoded in such called hight and low surrogates
// each 16 bit wide that needs to be send after
// another to the SendInput function without
// being interrupted by "keyup"
let result = c.encode_utf16(&mut buffer);
if result.len() == 1 {
self.unicode_key_click(result[0]);
} else {
for utf16_surrogate in result {
self.unicode_key_down(utf16_surrogate.clone());
}
// do i need to produce a keyup?
// self.unicode_key_up(0);
}
}
}
fn key_click(&mut self, key: Key) {
let scancode = self.key_to_scancode(key);
keybd_event(KEYEVENTF_SCANCODE, 0, scancode);
keybd_event(KEYEVENTF_KEYUP | KEYEVENTF_SCANCODE, 0, scancode);
}
fn key_down(&mut self, key: Key) -> crate::ResultType {
let res = keybd_event(KEYEVENTF_SCANCODE, 0, self.key_to_scancode(key));
if res == 0 {
let err = get_error();
if !err.is_empty() {
return Err(err.into());
}
}
Ok(())
}
fn key_up(&mut self, key: Key) {
keybd_event(
KEYEVENTF_KEYUP | KEYEVENTF_SCANCODE,
0,
self.key_to_scancode(key),
);
}
fn get_key_state(&mut self, key: Key) -> bool {
let keycode = self.key_to_keycode(key);
let x = unsafe { GetKeyState(keycode as _) };
if key == Key::CapsLock || key == Key::NumLock || key == Key::Scroll {
return (x & 0x1) == 0x1;
}
return (x as u16 & 0x8000) == 0x8000;
}
}
impl Enigo {
/// Gets the (width, height) of the main display in screen coordinates (pixels).
///
/// # Example
///
/// ```no_run
/// use enigo::*;
/// let mut size = Enigo::main_display_size();
/// ```
pub fn main_display_size() -> (usize, usize) {
let w = unsafe { GetSystemMetrics(SM_CXSCREEN) as usize };
let h = unsafe { GetSystemMetrics(SM_CYSCREEN) as usize };
(w, h)
}
/// Gets the location of mouse in screen coordinates (pixels).
///
/// # Example
///
/// ```no_run
/// use enigo::*;
/// let mut location = Enigo::mouse_location();
/// ```
pub fn mouse_location() -> (i32, i32) {
let mut point = POINT { x: 0, y: 0 };
let result = unsafe { GetCursorPos(&mut point) };
if result != 0 {
(point.x, point.y)
} else {
(0, 0)
}
}
fn unicode_key_click(&self, unicode_char: u16) {
self.unicode_key_down(unicode_char);
self.unicode_key_up(unicode_char);
}
fn unicode_key_down(&self, unicode_char: u16) {
keybd_event(KEYEVENTF_UNICODE, 0, unicode_char);
}
fn unicode_key_up(&self, unicode_char: u16) {
keybd_event(KEYEVENTF_UNICODE | KEYEVENTF_KEYUP, 0, unicode_char);
}
fn key_to_keycode(&self, key: Key) -> u16 {
// do not use the codes from crate winapi they're
// wrongly typed with i32 instead of i16 use the
// ones provided by win/keycodes.rs that are prefixed
// with an 'E' infront of the original name
#[allow(deprecated)]
// I mean duh, we still need to support deprecated keys until they're removed
match key {
Key::Alt => EVK_MENU,
Key::Backspace => EVK_BACK,
Key::CapsLock => EVK_CAPITAL,
Key::Control => EVK_LCONTROL,
Key::Delete => EVK_DELETE,
Key::DownArrow => EVK_DOWN,
Key::End => EVK_END,
Key::Escape => EVK_ESCAPE,
Key::F1 => EVK_F1,
Key::F10 => EVK_F10,
Key::F11 => EVK_F11,
Key::F12 => EVK_F12,
Key::F2 => EVK_F2,
Key::F3 => EVK_F3,
Key::F4 => EVK_F4,
Key::F5 => EVK_F5,
Key::F6 => EVK_F6,
Key::F7 => EVK_F7,
Key::F8 => EVK_F8,
Key::F9 => EVK_F9,
Key::Home => EVK_HOME,
Key::LeftArrow => EVK_LEFT,
Key::Option => EVK_MENU,
Key::PageDown => EVK_NEXT,
Key::PageUp => EVK_PRIOR,
Key::Return => EVK_RETURN,
Key::RightArrow => EVK_RIGHT,
Key::Shift => EVK_SHIFT,
Key::Space => EVK_SPACE,
Key::Tab => EVK_TAB,
Key::UpArrow => EVK_UP,
Key::Numpad0 => EVK_NUMPAD0,
Key::Numpad1 => EVK_NUMPAD1,
Key::Numpad2 => EVK_NUMPAD2,
Key::Numpad3 => EVK_NUMPAD3,
Key::Numpad4 => EVK_NUMPAD4,
Key::Numpad5 => EVK_NUMPAD5,
Key::Numpad6 => EVK_NUMPAD6,
Key::Numpad7 => EVK_NUMPAD7,
Key::Numpad8 => EVK_NUMPAD8,
Key::Numpad9 => EVK_NUMPAD9,
Key::Cancel => EVK_CANCEL,
Key::Clear => EVK_CLEAR,
Key::Menu => EVK_MENU,
Key::Pause => EVK_PAUSE,
Key::Kana => EVK_KANA,
Key::Hangul => EVK_HANGUL,
Key::Junja => EVK_JUNJA,
Key::Final => EVK_FINAL,
Key::Hanja => EVK_HANJA,
Key::Kanji => EVK_KANJI,
Key::Convert => EVK_CONVERT,
Key::Select => EVK_SELECT,
Key::Print => EVK_PRINT,
Key::Execute => EVK_EXECUTE,
Key::Snapshot => EVK_SNAPSHOT,
Key::Insert => EVK_INSERT,
Key::Help => EVK_HELP,
Key::Sleep => EVK_SLEEP,
Key::Separator => EVK_SEPARATOR,
Key::Mute => EVK_VOLUME_MUTE,
Key::VolumeDown => EVK_VOLUME_DOWN,
Key::VolumeUp => EVK_VOLUME_UP,
Key::Scroll => EVK_SCROLL,
Key::NumLock => EVK_NUMLOCK,
Key::RWin => EVK_RWIN,
Key::Apps => EVK_APPS,
Key::Add => EVK_ADD,
Key::Multiply => EVK_MULTIPLY,
Key::Decimal => EVK_DECIMAL,
Key::Subtract => EVK_SUBTRACT,
Key::Divide => EVK_DIVIDE,
Key::NumpadEnter => EVK_RETURN,
Key::Equals => '=' as _,
Key::Raw(raw_keycode) => raw_keycode,
Key::Layout(c) => self.get_layoutdependent_keycode(c.to_string()),
Key::Super | Key::Command | Key::Windows | Key::Meta => EVK_LWIN,
}
}
fn key_to_scancode(&self, key: Key) -> u16 {
let keycode = self.key_to_keycode(key);
unsafe { MapVirtualKeyW(keycode as u32, 0) as u16 }
}
fn get_layoutdependent_keycode(&self, string: String) -> u16 {
// get the first char from the string ignore the rest
// ensure its not a multybyte char
if let Some(chr) = string.chars().nth(0) {
// NOTE VkKeyScanW uses the current keyboard layout
// to specify a layout use VkKeyScanExW and GetKeyboardLayout
// or load one with LoadKeyboardLayoutW
let keycode_and_shiftstate = unsafe { VkKeyScanW(chr as _) };
keycode_and_shiftstate as _
} else {
0
}
}
}