mirror of
https://github.com/weyne85/rustdesk.git
synced 2025-10-29 17:00:05 +00:00
source code
This commit is contained in:
25
libs/enigo/.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
25
libs/enigo/.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal 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.
|
||||
20
libs/enigo/.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
libs/enigo/.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal 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.
|
||||
19
libs/enigo/.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
19
libs/enigo/.github/ISSUE_TEMPLATE/question.md
vendored
Normal 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
14
libs/enigo/.gitignore
vendored
Normal 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
15
libs/enigo/.travis.yml
Normal 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
13
libs/enigo/.vscode/launch.json
vendored
Normal 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
41
libs/enigo/Cargo.toml
Normal 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
21
libs/enigo/LICENSE
Normal 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
46
libs/enigo/README.md
Normal file
@@ -0,0 +1,46 @@
|
||||
[](https://travis-ci.org/enigo-rs/enigo)
|
||||
[](https://ci.appveyor.com/project/pythoneer/enigo-85xiy)
|
||||
[](https://dependencyci.com/github/pythoneer/enigo)
|
||||
[](https://docs.rs/enigo)
|
||||
[](https://crates.io/crates/enigo)
|
||||
[](https://discord.gg/Eb8CsnN)
|
||||
[](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
121
libs/enigo/appveyor.yml
Normal 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
61
libs/enigo/build.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
11
libs/enigo/examples/dsl.rs
Normal file
11
libs/enigo/examples/dsl.rs
Normal 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}");
|
||||
}
|
||||
12
libs/enigo/examples/key.rs
Normal file
12
libs/enigo/examples/key.rs
Normal 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'));
|
||||
}
|
||||
16
libs/enigo/examples/keyboard.rs
Normal file
16
libs/enigo/examples/keyboard.rs
Normal 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);
|
||||
}
|
||||
37
libs/enigo/examples/mouse.rs
Normal file
37
libs/enigo/examples/mouse.rs
Normal 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);
|
||||
}
|
||||
22
libs/enigo/examples/timer.rs
Normal file
22
libs/enigo/examples/timer.rs
Normal 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
1
libs/enigo/rustfmt.toml
Normal file
@@ -0,0 +1 @@
|
||||
wrap_comments = true
|
||||
184
libs/enigo/src/dsl.rs
Normal file
184
libs/enigo/src/dsl.rs
Normal 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
525
libs/enigo/src/lib.rs
Normal 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
361
libs/enigo/src/linux.rs
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
72
libs/enigo/src/macos/keycodes.rs
Normal file
72
libs/enigo/src/macos/keycodes.rs
Normal 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;
|
||||
680
libs/enigo/src/macos/macos_impl.rs
Normal file
680
libs/enigo/src/macos/macos_impl.rs
Normal 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 {}
|
||||
4
libs/enigo/src/macos/mod.rs
Normal file
4
libs/enigo/src/macos/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
mod macos_impl;
|
||||
|
||||
pub mod keycodes;
|
||||
pub use self::macos_impl::Enigo;
|
||||
73
libs/enigo/src/win/keycodes.rs
Normal file
73
libs/enigo/src/win/keycodes.rs
Normal 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;
|
||||
4
libs/enigo/src/win/mod.rs
Normal file
4
libs/enigo/src/win/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
mod win_impl;
|
||||
|
||||
pub mod keycodes;
|
||||
pub use self::win_impl::Enigo;
|
||||
366
libs/enigo/src/win/win_impl.rs
Normal file
366
libs/enigo/src/win/win_impl.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user