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

View File

@@ -0,0 +1,25 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug, needs investigation
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps or a minimal code example to reproduce the behavior.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Environment (please complete the following information):**
- OS: [e.g. Linux, Windows, macOS ..]
- Rust [e.g. rustc --version]
- Library Version [e.g. enigo 0.0.13 or commit hash fa448be ]
**Additional context**
Add any other context about the problem here.

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement, needs investigation
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -0,0 +1,19 @@
---
name: Question
about: Ask your Question here
title: ''
labels: question
assignees: ''
---
**Describe your Question**
A clear and concise description of what you want to know.
**Describe your Goal**
A clear and concise description of what you want to achieve. Consider the [XYProblem](http://xyproblem.info/)
**Environment (please complete the following information):**
- OS: [e.g. Linux, Windows, macOS ..]
- Rust [e.g. rustc --version]
- Library Version [e.g. enigo 0.0.13 or commit hash fa448be ]

14
libs/enigo/.gitignore vendored Normal file
View File

@@ -0,0 +1,14 @@
.DS_Store
# Generated by Cargo
# will have compiled files and executables
/target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
Cargo.lock
# RustFmt files
**/*.rs.bk
# intellij
.idea

15
libs/enigo/.travis.yml Normal file
View File

@@ -0,0 +1,15 @@
language: rust
rust:
- stable
- beta
- nightly
matrix:
allow_failures:
- rust: nightly
before_install:
- if [ "$TRAVIS_OS_NAME" == "linux" ]; then sudo apt-get -qq update; fi
- if [ "$TRAVIS_OS_NAME" == "linux" ]; then sudo apt-get install -y libxdo-dev; fi
os:
- linux
- osx

13
libs/enigo/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,13 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug",
"type": "gdb",
"request": "launch",
"target": "./target/debug/examples/keyboard",
"cwd": "${workspaceRoot}"
}
]
}

41
libs/enigo/Cargo.toml Normal file
View File

@@ -0,0 +1,41 @@
[package]
name = "enigo"
version = "0.0.14"
authors = ["Dustin Bensing <dustin.bensing@googlemail.com>"]
edition = "2018"
build = "build.rs"
description = "Enigo lets you control your mouse and keyboard in an abstract way on different operating systems (currently only Linux, macOS, Win Redox and *BSD planned)"
documentation = "https://docs.rs/enigo/"
homepage = "https://github.com/enigo-rs/enigo"
repository = "https://github.com/enigo-rs/enigo"
readme = "README.md"
keywords = ["input", "mouse", "testing", "keyboard", "automation"]
categories = ["development-tools::testing", "api-bindings", "hardware-support"]
license = "MIT"
[badges]
travis-ci = { repository = "enigo-rs/enigo" }
appveyor = { repository = "pythoneer/enigo-85xiy" }
[dependencies]
serde = { version = "1.0", optional = true }
serde_derive = { version = "1.0", optional = true }
log = "0.4"
[features]
with_serde = ["serde", "serde_derive"]
[target.'cfg(target_os = "windows")'.dependencies]
winapi = { version = "0.3", features = ["winuser", "winbase"] }
[target.'cfg(target_os = "macos")'.dependencies]
core-graphics = "0.22"
objc = "0.2"
unicode-segmentation = "1.6"
[target.'cfg(target_os = "linux")'.dependencies]
libc = "0.2"
[build-dependencies]
pkg-config = "0.3"

21
libs/enigo/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 pythoneer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

46
libs/enigo/README.md Normal file
View File

@@ -0,0 +1,46 @@
[![Build Status](https://travis-ci.org/enigo-rs/enigo.svg?branch=master)](https://travis-ci.org/enigo-rs/enigo)
[![Build status](https://ci.appveyor.com/api/projects/status/6cd00pajx4tvvl3e?svg=true)](https://ci.appveyor.com/project/pythoneer/enigo-85xiy)
[![Dependency Status](https://dependencyci.com/github/pythoneer/enigo/badge)](https://dependencyci.com/github/pythoneer/enigo)
[![Docs](https://docs.rs/enigo/badge.svg)](https://docs.rs/enigo)
[![Crates.io](https://img.shields.io/crates/v/enigo.svg)](https://crates.io/crates/enigo)
[![Discord chat](https://img.shields.io/discord/315925376486342657.svg)](https://discord.gg/Eb8CsnN)
[![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/enigo-rs/Lobby)
# enigo
Cross platform input simulation in Rust!
- [x] Linux (X11) mouse
- [x] Linux (X11) text
- [ ] Linux (Wayland) mouse
- [ ] Linux (Wayland) text
- [x] MacOS mouse
- [x] MacOS text
- [x] Win mouse
- [x] Win text
- [x] Custom Parser
```Rust
let mut enigo = Enigo::new();
enigo.mouse_move_to(500, 200);
enigo.mouse_click(MouseButton::Left);
enigo.key_sequence_parse("{+CTRL}a{-CTRL}{+SHIFT}Hello World{-SHIFT}");
```
for more look at examples
Runtime dependencies
--------------------
Linux users may have to install libxdo-dev. For example, on Ubuntu:
```Bash
apt install libxdo-dev
```
On Arch:
```Bash
pacman -S xdotool
```

121
libs/enigo/appveyor.yml Normal file
View File

@@ -0,0 +1,121 @@
# Appveyor configuration template for Rust using rustup for Rust installation
# https://github.com/starkat99/appveyor-rust
## Operating System (VM environment) ##
# Rust needs at least Visual Studio 2013 Appveyor OS for MSVC targets.
os: Visual Studio 2015
## Build Matrix ##
# This configuration will setup a build for each channel & target combination (12 windows
# combinations in all).
#
# There are 3 channels: stable, beta, and nightly.
#
# Alternatively, the full version may be specified for the channel to build using that specific
# version (e.g. channel: 1.5.0)
#
# The values for target are the set of windows Rust build targets. Each value is of the form
#
# ARCH-pc-windows-TOOLCHAIN
#
# Where ARCH is the target architecture, either x86_64 or i686, and TOOLCHAIN is the linker
# toolchain to use, either msvc or gnu. See https://www.rust-lang.org/downloads.html#win-foot for
# a description of the toolchain differences.
# See https://github.com/rust-lang-nursery/rustup.rs/#toolchain-specification for description of
# toolchains and host triples.
#
# Comment out channel/target combos you do not wish to build in CI.
#
# You may use the `cargoflags` and `RUSTFLAGS` variables to set additional flags for cargo commands
# and rustc, respectively. For instance, you can uncomment the cargoflags lines in the nightly
# channels to enable unstable features when building for nightly. Or you could add additional
# matrix entries to test different combinations of features.
environment:
matrix:
### MSVC Toolchains ###
# Stable 64-bit MSVC
- channel: stable
target: x86_64-pc-windows-msvc
# Stable 32-bit MSVC
- channel: stable
target: i686-pc-windows-msvc
# Beta 64-bit MSVC
- channel: beta
target: x86_64-pc-windows-msvc
# Beta 32-bit MSVC
- channel: beta
target: i686-pc-windows-msvc
# Nightly 64-bit MSVC
- channel: nightly
target: x86_64-pc-windows-msvc
#cargoflags: --features "unstable"
# Nightly 32-bit MSVC
- channel: nightly
target: i686-pc-windows-msvc
#cargoflags: --features "unstable"
### GNU Toolchains ###
# Stable 64-bit GNU
- channel: stable
target: x86_64-pc-windows-gnu
# Stable 32-bit GNU
- channel: stable
target: i686-pc-windows-gnu
# Beta 64-bit GNU
- channel: beta
target: x86_64-pc-windows-gnu
# Beta 32-bit GNU
- channel: beta
target: i686-pc-windows-gnu
# Nightly 64-bit GNU
- channel: nightly
target: x86_64-pc-windows-gnu
#cargoflags: --features "unstable"
# Nightly 32-bit GNU
- channel: nightly
target: i686-pc-windows-gnu
#cargoflags: --features "unstable"
### Allowed failures ###
# See Appveyor documentation for specific details. In short, place any channel or targets you wish
# to allow build failures on (usually nightly at least is a wise choice). This will prevent a build
# or test failure in the matching channels/targets from failing the entire build.
matrix:
allow_failures:
- channel: nightly
# If you only care about stable channel build failures, uncomment the following line:
#- channel: beta
## Install Script ##
# This is the most important part of the Appveyor configuration. This installs the version of Rust
# specified by the 'channel' and 'target' environment variables from the build matrix. This uses
# rustup to install Rust.
#
# For simple configurations, instead of using the build matrix, you can simply set the
# default-toolchain and default-host manually here.
install:
- appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
- rustup-init -yv --default-toolchain %channel% --default-host %target%
- set PATH=%PATH%;%USERPROFILE%\.cargo\bin
- rustc -vV
- cargo -vV
## Build Script ##
# 'cargo test' takes care of building for us, so disable Appveyor's build stage. This prevents
# the "directory does not contain a project or solution file" error.
build: false
# Uses 'cargo test' to run tests and build. Alternatively, the project may call compiled programs
#directly or perform other testing commands. Rust will automatically be placed in the PATH
# environment variable.
test_script:
- cargo test --verbose %cargoflags%

61
libs/enigo/build.rs Normal file
View File

@@ -0,0 +1,61 @@
#[cfg(target_os = "windows")]
fn main() {}
#[cfg(target_os = "macos")]
fn main() {}
#[cfg(target_os = "linux")]
use pkg_config;
#[cfg(target_os = "linux")]
use std::env;
#[cfg(target_os = "linux")]
use std::fs::File;
#[cfg(target_os = "linux")]
use std::io::Write;
#[cfg(target_os = "linux")]
use std::path::Path;
#[cfg(target_os = "linux")]
fn main() {
let libraries = [
"xext",
"gl",
"xcursor",
"xxf86vm",
"xft",
"xinerama",
"xi",
"x11",
"xlib_xcb",
"xmu",
"xrandr",
"xtst",
"xrender",
"xscrnsaver",
"xt",
];
let mut config = String::new();
for lib in libraries.iter() {
let libdir = match pkg_config::get_variable(lib, "libdir") {
Ok(libdir) => format!("Some(\"{}\")", libdir),
Err(_) => "None".to_string(),
};
config.push_str(&format!(
"pub const {}: Option<&'static str> = {};\n",
lib, libdir
));
}
let config = format!("pub mod config {{ pub mod libdir {{\n{}}}\n}}", config);
let out_dir = env::var("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("config.rs");
let mut f = File::create(&dest_path).unwrap();
f.write_all(&config.into_bytes()).unwrap();
let target = env::var("TARGET").unwrap();
if target.contains("linux") {
println!("cargo:rustc-link-lib=dl");
} else if target.contains("freebsd") || target.contains("dragonfly") {
println!("cargo:rustc-link-lib=c");
}
}

View File

@@ -0,0 +1,11 @@
use enigo::{Enigo, KeyboardControllable};
use std::thread;
use std::time::Duration;
fn main() {
thread::sleep(Duration::from_secs(2));
let mut enigo = Enigo::new();
// write text and select all
enigo.key_sequence_parse("{+UNICODE}{{Hello World!}} ❤️{-UNICODE}{+CTRL}a{-CTRL}");
}

View File

@@ -0,0 +1,12 @@
use enigo::{Enigo, Key, KeyboardControllable};
use std::thread;
use std::time::Duration;
fn main() {
thread::sleep(Duration::from_secs(2));
let mut enigo = Enigo::new();
enigo.key_down(Key::Layout('a'));
thread::sleep(Duration::from_secs(1));
enigo.key_up(Key::Layout('a'));
}

View File

@@ -0,0 +1,16 @@
use enigo::{Enigo, Key, KeyboardControllable};
use std::thread;
use std::time::Duration;
fn main() {
thread::sleep(Duration::from_secs(2));
let mut enigo = Enigo::new();
// write text
enigo.key_sequence("Hello World! here is a lot of text ❤️");
// select all
enigo.key_down(Key::Control);
enigo.key_click(Key::Layout('a'));
enigo.key_up(Key::Control);
}

View File

@@ -0,0 +1,37 @@
use enigo::{Enigo, MouseButton, MouseControllable};
use std::thread;
use std::time::Duration;
fn main() {
let wait_time = Duration::from_secs(2);
let mut enigo = Enigo::new();
thread::sleep(wait_time);
enigo.mouse_move_to(500, 200);
thread::sleep(wait_time);
enigo.mouse_down(MouseButton::Left);
thread::sleep(wait_time);
enigo.mouse_move_relative(100, 100);
thread::sleep(wait_time);
enigo.mouse_up(MouseButton::Left);
thread::sleep(wait_time);
enigo.mouse_click(MouseButton::Left);
thread::sleep(wait_time);
enigo.mouse_scroll_x(2);
thread::sleep(wait_time);
enigo.mouse_scroll_x(-2);
thread::sleep(wait_time);
enigo.mouse_scroll_y(2);
thread::sleep(wait_time);
enigo.mouse_scroll_y(-2);
thread::sleep(wait_time);
}

View File

@@ -0,0 +1,22 @@
use enigo::{Enigo, Key, KeyboardControllable};
use std::thread;
use std::time::Duration;
use std::time::Instant;
fn main() {
thread::sleep(Duration::from_secs(2));
let mut enigo = Enigo::new();
let now = Instant::now();
// write text
enigo.key_sequence("Hello World! ❤️");
let time = now.elapsed();
println!("{:?}", time);
// select all
enigo.key_down(Key::Control);
enigo.key_click(Key::Layout('a'));
enigo.key_up(Key::Control);
}

1
libs/enigo/rustfmt.toml Normal file
View File

@@ -0,0 +1 @@
wrap_comments = true

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
}
}
}