├── .github └── workflows │ └── github-actions.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── config.yaml ├── readme.rs ├── test.rs └── using_config.rs └── src ├── bin └── mki.rs ├── details.rs ├── keyboard.rs ├── lib.rs ├── linux ├── keyboard_mouse.rs └── mod.rs ├── mouse.rs ├── parse.rs ├── sequence.rs └── windows ├── keyboard.rs ├── mod.rs └── mouse.rs /.github/workflows/github-actions.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: {} 3 | name: CI 4 | 5 | jobs: 6 | build_linux: 7 | name: Rust project 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: update apt 11 | run: sudo apt-get update 12 | - name: Install deps 13 | run: sudo apt-get -y install libinput-dev 14 | - uses: actions/checkout@v2 15 | - uses: actions-rs/toolchain@v1 16 | with: 17 | toolchain: stable 18 | - run: cargo build 19 | - run: cargo build --example readme 20 | - run: cargo build --example test 21 | - run: cargo build --example using_config 22 | build_windows: 23 | name: Rust project 24 | runs-on: windows-latest 25 | steps: 26 | - uses: actions/checkout@v2 27 | - uses: actions-rs/toolchain@v1 28 | with: 29 | toolchain: stable 30 | - run: cargo build 31 | - run: cargo build --example readme 32 | - run: cargo build --example test 33 | - run: cargo build --example using_config 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | .idea 4 | *.iml 5 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "autocfg" 7 | version = "1.3.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 10 | 11 | [[package]] 12 | name = "bitflags" 13 | version = "1.3.2" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 16 | 17 | [[package]] 18 | name = "byteorder" 19 | version = "1.5.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 22 | 23 | [[package]] 24 | name = "bytes" 25 | version = "0.4.12" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" 28 | dependencies = [ 29 | "byteorder", 30 | "iovec", 31 | ] 32 | 33 | [[package]] 34 | name = "cfg-if" 35 | version = "0.1.10" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 38 | 39 | [[package]] 40 | name = "cfg-if" 41 | version = "1.0.0" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 44 | 45 | [[package]] 46 | name = "custom_derive" 47 | version = "0.1.7" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" 50 | 51 | [[package]] 52 | name = "enum_derive" 53 | version = "0.1.7" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "406ac2a8c9eedf8af9ee1489bee9e50029278a6456c740f7454cf8a158abc816" 56 | 57 | [[package]] 58 | name = "gcc" 59 | version = "0.3.55" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" 62 | 63 | [[package]] 64 | name = "hashbrown" 65 | version = "0.12.3" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 68 | 69 | [[package]] 70 | name = "indexmap" 71 | version = "1.9.3" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 74 | dependencies = [ 75 | "autocfg", 76 | "hashbrown", 77 | ] 78 | 79 | [[package]] 80 | name = "input" 81 | version = "0.7.1" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "f95640ef27dac9b23ef1fbd760c67a88ce3cab2143a2c18390e71f39c53b815f" 84 | dependencies = [ 85 | "bitflags", 86 | "input-sys", 87 | "libc", 88 | "log", 89 | "udev", 90 | ] 91 | 92 | [[package]] 93 | name = "input-sys" 94 | version = "1.18.0" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "bd4f5b4d1c00331c5245163aacfe5f20be75b564c7112d45893d4ae038119eb0" 97 | 98 | [[package]] 99 | name = "ioctl-sys" 100 | version = "0.5.2" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "5e2c4b26352496eaaa8ca7cfa9bd99e93419d3f7983dc6e99c2a35fe9e33504a" 103 | 104 | [[package]] 105 | name = "iovec" 106 | version = "0.1.4" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" 109 | dependencies = [ 110 | "libc", 111 | ] 112 | 113 | [[package]] 114 | name = "lazy_static" 115 | version = "1.5.0" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 118 | 119 | [[package]] 120 | name = "libc" 121 | version = "0.2.159" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" 124 | 125 | [[package]] 126 | name = "libudev-sys" 127 | version = "0.1.4" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" 130 | dependencies = [ 131 | "libc", 132 | "pkg-config", 133 | ] 134 | 135 | [[package]] 136 | name = "linked-hash-map" 137 | version = "0.5.6" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" 140 | 141 | [[package]] 142 | name = "log" 143 | version = "0.4.22" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 146 | 147 | [[package]] 148 | name = "memoffset" 149 | version = "0.6.5" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" 152 | dependencies = [ 153 | "autocfg", 154 | ] 155 | 156 | [[package]] 157 | name = "mki" 158 | version = "0.2.2" 159 | dependencies = [ 160 | "input", 161 | "lazy_static", 162 | "libc", 163 | "nix 0.24.3", 164 | "serde", 165 | "serde_yaml", 166 | "uinput", 167 | "winapi", 168 | "x11", 169 | ] 170 | 171 | [[package]] 172 | name = "nix" 173 | version = "0.10.0" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "b7fd5681d13fda646462cfbd4e5f2051279a89a544d50eb98c365b507246839f" 176 | dependencies = [ 177 | "bitflags", 178 | "bytes", 179 | "cfg-if 0.1.10", 180 | "gcc", 181 | "libc", 182 | "void", 183 | ] 184 | 185 | [[package]] 186 | name = "nix" 187 | version = "0.24.3" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" 190 | dependencies = [ 191 | "bitflags", 192 | "cfg-if 1.0.0", 193 | "libc", 194 | "memoffset", 195 | ] 196 | 197 | [[package]] 198 | name = "pkg-config" 199 | version = "0.3.31" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" 202 | 203 | [[package]] 204 | name = "proc-macro2" 205 | version = "1.0.86" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 208 | dependencies = [ 209 | "unicode-ident", 210 | ] 211 | 212 | [[package]] 213 | name = "quote" 214 | version = "1.0.37" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 217 | dependencies = [ 218 | "proc-macro2", 219 | ] 220 | 221 | [[package]] 222 | name = "ryu" 223 | version = "1.0.18" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 226 | 227 | [[package]] 228 | name = "serde" 229 | version = "1.0.210" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" 232 | dependencies = [ 233 | "serde_derive", 234 | ] 235 | 236 | [[package]] 237 | name = "serde_derive" 238 | version = "1.0.210" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" 241 | dependencies = [ 242 | "proc-macro2", 243 | "quote", 244 | "syn", 245 | ] 246 | 247 | [[package]] 248 | name = "serde_yaml" 249 | version = "0.8.26" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" 252 | dependencies = [ 253 | "indexmap", 254 | "ryu", 255 | "serde", 256 | "yaml-rust", 257 | ] 258 | 259 | [[package]] 260 | name = "syn" 261 | version = "2.0.77" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" 264 | dependencies = [ 265 | "proc-macro2", 266 | "quote", 267 | "unicode-ident", 268 | ] 269 | 270 | [[package]] 271 | name = "udev" 272 | version = "0.6.3" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "1c960764f7e816eed851a96c364745d37f9fe71a2e7dba79fbd40104530b5dd0" 275 | dependencies = [ 276 | "libc", 277 | "libudev-sys", 278 | "pkg-config", 279 | ] 280 | 281 | [[package]] 282 | name = "uinput" 283 | version = "0.1.3" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "8b074d55c90be32a89a063fe3f944c0ceed0a8e3291369a99809f18fa326685b" 286 | dependencies = [ 287 | "custom_derive", 288 | "enum_derive", 289 | "libc", 290 | "nix 0.10.0", 291 | "uinput-sys", 292 | ] 293 | 294 | [[package]] 295 | name = "uinput-sys" 296 | version = "0.1.7" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "9aabddd8174ccadd600afeab346bb276cb1db5fafcf6a7c5c5708b8cc4b2cac7" 299 | dependencies = [ 300 | "ioctl-sys", 301 | "libc", 302 | ] 303 | 304 | [[package]] 305 | name = "unicode-ident" 306 | version = "1.0.13" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 309 | 310 | [[package]] 311 | name = "void" 312 | version = "1.0.2" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 315 | 316 | [[package]] 317 | name = "winapi" 318 | version = "0.3.9" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 321 | dependencies = [ 322 | "winapi-i686-pc-windows-gnu", 323 | "winapi-x86_64-pc-windows-gnu", 324 | ] 325 | 326 | [[package]] 327 | name = "winapi-i686-pc-windows-gnu" 328 | version = "0.4.0" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 331 | 332 | [[package]] 333 | name = "winapi-x86_64-pc-windows-gnu" 334 | version = "0.4.0" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 337 | 338 | [[package]] 339 | name = "x11" 340 | version = "2.21.0" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" 343 | dependencies = [ 344 | "libc", 345 | "pkg-config", 346 | ] 347 | 348 | [[package]] 349 | name = "yaml-rust" 350 | version = "0.4.5" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" 353 | dependencies = [ 354 | "linked-hash-map", 355 | ] 356 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mki" 3 | version = "0.2.2" 4 | description = "Windows and Linux library for registring global input hooks and simulating keyboard and mouse events." 5 | authors = ["fulara "] 6 | keywords = ["mouse", "keyboard", "input", "hook", "autohotkey"] 7 | readme = "README.md" 8 | homepage = "https://github.com/fulara/mki-rust" 9 | repository = "https://github.com/fulara/mki-rust" 10 | categories = ["api-bindings"] 11 | documentation = "https://docs.rs/mki-rust" 12 | license = "MIT" 13 | edition = "2018" 14 | 15 | [dependencies] 16 | lazy_static = "1" 17 | libc = "0.2" 18 | serde = { version = "1", features = ["derive"]} 19 | serde_yaml = "0.8" 20 | 21 | [target.'cfg(windows)'.dependencies] 22 | winapi = { version = "0.3.9", features = ["winuser"] } 23 | 24 | [target.'cfg(target_os="linux")'.dependencies] 25 | input = "0.7" 26 | nix = "0.24" 27 | uinput = { version = "0.1.3", default-features = false } 28 | x11 = { version = "2", features = ["xlib", "xtest"] } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy 2 | of this software and associated documentation files (the "Software"), to deal 3 | in the Software without restriction, including without limitation the rights 4 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 5 | copies of the Software, and to permit persons to whom the Software is 6 | furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all 9 | copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | SOFTWARE. 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mki - mouse-keyboard-input [![crates.io version](https://img.shields.io/crates/v/mki.svg)](https://crates.io/crates/mki) 2 | Windows & Linux library for registring global input hooks and simulating keyboard and mouse events. 3 | 4 | ## Features: 5 | * Install a global key/mouse event handler without binding to individual keys. 6 | * Install a per key/button event handlers. 7 | * Bind action on key presses. 8 | * Register hotkeys combinations such as CTRL+Q and have action invoked on them. 9 | 10 | ## Sample: 11 | Check examples/readme.rs for the example. Can be run with `cargo run --example readme`. 12 | 13 | ```rust 14 | use mki::{bind_key, Action, InhibitEvent, Keyboard, Sequence}; 15 | use std::thread; 16 | use std::time::Duration; 17 | 18 | fn main() { 19 | Keyboard::A.bind(|_| { 20 | println!("A pressed, sending B"); 21 | Keyboard::B.click(); 22 | }); 23 | mki::bind_any_key(Action::handle_kb(|key| { 24 | use Keyboard::*; 25 | if matches!(key, S | L | O | W | LeftShift | LeftControl | B) { 26 | // Ignore outputs from other commands for nicer output 27 | } else { 28 | println!("Some key pressed pressed: {:?}", key); 29 | } 30 | })); 31 | mki::bind_any_button(Action::handle_mouse(|button| { 32 | println!("Mouse button pressed {:?}", button); 33 | })); 34 | mki::register_hotkey(&[Keyboard::LeftControl, Keyboard::B], || { 35 | println!("Ctrl+B Pressed") 36 | }); 37 | mki::bind_key( 38 | Keyboard::S, 39 | Action::sequencing_kb(|_| { 40 | Sequence::text("LLLLLow").unwrap().send(); 41 | thread::sleep(Duration::from_secs(1)); 42 | }), 43 | ); 44 | 45 | // This binds action to a W key, 46 | // that W press will not be sent to the following services ( only on windows ) 47 | // whenever Caps Lock is toggled 48 | // Action will be executed on separate thread. 49 | bind_key( 50 | Keyboard::W, 51 | Action { 52 | callback: Box::new(|event, state| { 53 | println!("key: {:?} changed state now is: {:?}", event, state); 54 | }), 55 | inhibit: InhibitEvent::maybe(|| { 56 | if Keyboard::CapsLock.is_toggled() { 57 | InhibitEvent::Yes 58 | } else { 59 | InhibitEvent::No 60 | } 61 | }), 62 | sequencer: false, 63 | defer: true, 64 | }, 65 | ); 66 | 67 | thread::sleep(Duration::from_secs(100)); 68 | } 69 | ``` 70 | ## Sample2: config file 71 | Library supports loading hotkey scripting using a yaml configuration file. check `using_config.rs` 72 | example to see more elaborate example. Below is a minimal one that speaks for itself. 73 | ``` 74 | --- 75 | bind: 76 | - description: Whenever Ctrl+L is clicked click K as well 77 | key: 78 | - LeftControl 79 | - L 80 | action: 81 | click: 82 | key: 83 | - K 84 | ``` 85 | 86 | a Library provides a binary `mki` that can be used to load the script. 87 | 88 | ## mki binary 89 | Library providers a mki binary that takes 1 argument a path to a .yaml file, 90 | the File will be parsed, it will print kind of hotkeys it registers and listen infinitely for the input. 91 | 92 | It is really a mini hotkey application. 93 | 94 | ## Threading model 95 | It is strongly advised to use a default `bind` which will spawn new thread for the bindings. 96 | There is an option to `sequence` the events which will cause them to be invoked one after another in a separate thread. 97 | An option to `callback` the event causes invocation of the detected thread. 98 | 99 | Nomenclature used: 100 | * `handle` -> spawns a new thread. 101 | * `sequence` -> enqueues given event in a single thread that handles all the events one after another. 102 | * `callback` -> callbacks on the same thread as the key was detected on, recommended not to block that thread nor 103 | schedule other key presses as it may result in handler being silently deregistered. 104 | 105 | ## Linux 106 | Note that running the app on Linux requires root. 107 | 108 | ### Linux dependencies: 109 | *libxtst-dev* 110 | 111 | ### Linux caveats 112 | 113 | Currently the linux implementation will sleep for 100ms upon first invocation of the library. 114 | Otherwise some initial key strokes are missed. 115 | 116 | ##### cross development linux -> windows 117 | cross. 118 | 119 | to cross compile windows on linux: 120 | ```rust 121 | cargo install cross 122 | cross check --target x86_64-pc-windows-gnu 123 | 124 | ``` 125 | #### TODOs: 126 | * Should `are_pressed` support Mouse? for now the `Pressed` in config ignores mouse. 127 | * Get Mouse Position on linux and windows missing. 128 | 129 | ##### Future Eventual Considerations 130 | * Ditch those static states that initialize god knows when, instead introduce a `Context`. 131 | However the callbacks from libraries will still require a global accessor, but it will defintiely be better. 132 | * Should sequenced be removed and instead channel way be introduced?. 133 | By storing a Vec of TX could just propagate the events. 134 | This has the problem that it will be a bit awkward to send the mouse pos. 135 | Because of the State enum being separated from the event ones. 136 | Maybe the State should be deleted and instead the enums would have Up and down? 137 | That would make the keyboard enum a bit hectic though. 138 | 139 | #### 0.3 release plan: 140 | 141 | * It would be nice to support multi screen on windows. 142 | atm I don't have multi screens so hard to test. 143 | * Linux to support mouse live tracking callback 144 | * Add a debug mode - --debug that prints whenever anything that has registered hotkey is clicked 145 | 146 | # Changelog 147 | #### 0.2.2 148 | * Deps update, fixed some issues with linux #3 149 | #### 0.2 release: 150 | * Mouse to support location. 151 | * Windows to support mouse live tracking callback. 152 | * Mouse to support sending key strokes at given coordinates - added click_at. 153 | * Linux display usage is ultra ugly right now, just change it to a lambda. 154 | * Various api breaking changes. 155 | 156 | 157 | # Support 158 | If you want to show appreciation for the stuff this repo gave you - you can do so via https://www.buymeacoffee.com/fulara 159 | -------------------------------------------------------------------------------- /examples/config.yaml: -------------------------------------------------------------------------------- 1 | # This is just a minimal example for the mki binary. 2 | # Usage would be: cargo run --bin mki examples/config.yaml 3 | --- 4 | bind: 5 | - description: Whenever Ctrl+L is clicked click K as well 6 | key: 7 | - LeftControl 8 | - L 9 | action: 10 | click: 11 | key: 12 | - K 13 | -------------------------------------------------------------------------------- /examples/readme.rs: -------------------------------------------------------------------------------- 1 | use mki::{bind_key, Action, InhibitEvent, Keyboard, Sequence}; 2 | use std::thread; 3 | use std::time::Duration; 4 | 5 | fn main() { 6 | Keyboard::A.bind(|_| { 7 | println!("A pressed, sending B"); 8 | Keyboard::B.click(); 9 | }); 10 | mki::bind_any_key(Action::handle_kb(|key| { 11 | use Keyboard::*; 12 | if matches!(key, S | L | O | W | LeftShift | LeftControl | B) { 13 | // Ignore outputs from other commands for nicer output 14 | } else { 15 | println!("Some key pressed pressed: {:?}", key); 16 | } 17 | })); 18 | mki::bind_any_button(Action::handle_mouse(|button| { 19 | println!("Mouse button pressed {:?}", button); 20 | })); 21 | mki::register_hotkey(&[Keyboard::LeftControl, Keyboard::B], || { 22 | println!("Ctrl+B Pressed") 23 | }); 24 | mki::bind_key( 25 | Keyboard::S, 26 | Action::sequencing_kb(|_| { 27 | Sequence::text("LLLLLow").unwrap().send(); 28 | thread::sleep(Duration::from_secs(1)); 29 | }), 30 | ); 31 | 32 | // This binds action to a W key, 33 | // that W press will not be sent to the following services ( only on windows ) 34 | // whenever Caps Lock is toggled 35 | // Action will be executed on separate thread. 36 | bind_key( 37 | Keyboard::W, 38 | Action { 39 | callback: Box::new(|event, state| { 40 | println!("key: {:?} changed state now is: {:?}", event, state); 41 | }), 42 | inhibit: InhibitEvent::maybe(|| { 43 | if Keyboard::CapsLock.is_toggled() { 44 | InhibitEvent::Yes 45 | } else { 46 | InhibitEvent::No 47 | } 48 | }), 49 | sequencer: false, 50 | defer: true, 51 | }, 52 | ); 53 | 54 | thread::sleep(Duration::from_secs(100)); 55 | } 56 | -------------------------------------------------------------------------------- /examples/test.rs: -------------------------------------------------------------------------------- 1 | use mki::*; 2 | use std::sync::atomic::{AtomicI64, Ordering}; 3 | use std::thread; 4 | use std::time::Duration; 5 | 6 | fn main() { 7 | use Keyboard::*; 8 | bind_any_key(Action::handle_kb(|key| { 9 | println!("Pressed: {:?}", key); 10 | })); 11 | A.bind(|_| { 12 | println!("AAAAAAnd we have a winner."); 13 | }); 14 | 15 | S.act_on(Action::sequencing_kb(|_| { 16 | println!("\nOkay mimicking a very slow operation... (1s sleep)"); 17 | thread::sleep(Duration::from_millis(1000)); 18 | println!("\nOkay action done (100ms sleep)"); 19 | thread::sleep(Duration::from_millis(100)); 20 | })); 21 | 22 | R.bind(|_| { 23 | println!("R Pressed sending Q"); 24 | Q.click(); 25 | }); 26 | 27 | Mouse::Left.bind(|_| { 28 | println!("Left Mouse button pressed"); 29 | }); 30 | 31 | #[cfg(target_os = "windows")] // Not sure how to detect double on linux 32 | Mouse::DoubleLeft.bind(|_| { 33 | println!("Double Left Click Mouse"); 34 | }); 35 | 36 | Mouse::Right.bind(|_| { 37 | println!("Right Mouse button pressed"); 38 | }); 39 | 40 | O.bind(|_| loop { 41 | println!("Observing T: {}", T.is_pressed()); 42 | thread::sleep(Duration::from_secs(1)); 43 | }); 44 | 45 | for key in [T, H, I, S, Space, A, Space, T, E, S, T].iter() { 46 | key.click(); 47 | } 48 | 49 | register_hotkey(&[LeftControl, U], || println!("Ctrl+U pressed")); 50 | 51 | Mouse::track(|x, y| { 52 | static COUNTER: AtomicI64 = AtomicI64::new(0); 53 | if COUNTER.load(Ordering::Relaxed) % 100 == 0 { 54 | println!("mouse movement (once every 100 ticks): {} {} ", x, y) 55 | } 56 | COUNTER.fetch_add(1, Ordering::Relaxed); 57 | }); 58 | 59 | B.bind(|_| { 60 | // Clearing bind for nicer output. 61 | remove_any_key_bind(); 62 | R.clear_bind(); 63 | O.clear_bind(); 64 | S.clear_bind(); 65 | A.clear_bind(); 66 | 67 | // So this seems awfully laggy on Linux, interesting. Is something wrong with impl? 68 | Sequence::text("\nWill now type...:\nmini. very, mini1\nHello World.\nAnswer is: 42") 69 | .unwrap() 70 | .send(); 71 | }); 72 | thread::sleep(Duration::from_secs(100)); 73 | } 74 | -------------------------------------------------------------------------------- /examples/using_config.rs: -------------------------------------------------------------------------------- 1 | use mki::load_config; 2 | use std::thread; 3 | use std::time::Duration; 4 | 5 | fn main() { 6 | let cfg = r#"--- 7 | bind: 8 | - description: "LCtrl + H: [Loop until state is 1 [printing W, Sleep100]], then print E" 9 | key: 10 | - LeftControl 11 | - H 12 | action: 13 | multi: 14 | - while-state-matches: 15 | name: test 16 | value: "1" 17 | action: 18 | - click: 19 | key: 20 | - W 21 | - sleep: 100 22 | - click: 23 | key: 24 | - E 25 | - description: "S: Set state to 1 then print it" 26 | key: 27 | - S 28 | action: 29 | multi: 30 | - set-state: 31 | name: test 32 | value: "1" 33 | - print-state: test 34 | - description: "R: Set state to 0 then print it" 35 | key: 36 | - R 37 | action: 38 | multi: 39 | - set-state: 40 | name: test 41 | value: "0" 42 | - print-state: test 43 | - description: If state 1 then click 1; If state 0 then click 0 44 | key: 45 | - D 46 | action: 47 | multi: 48 | - state-matches: 49 | name: test 50 | value: "1" 51 | action: 52 | - click: 53 | key: 54 | - Number1 55 | - state-matches: 56 | name: test 57 | value: "0" 58 | action: 59 | - click: 60 | key: 61 | - Number0 62 | "#; 63 | load_config(cfg).unwrap(); 64 | thread::sleep(Duration::from_secs(1000)); 65 | } 66 | -------------------------------------------------------------------------------- /src/bin/mki.rs: -------------------------------------------------------------------------------- 1 | use mki::*; 2 | use std::env::args; 3 | use std::fs::File; 4 | use std::io::Read; 5 | use std::thread; 6 | use std::time::Duration; 7 | 8 | fn main() -> Result<(), serde_yaml::Error> { 9 | let cfg = args() 10 | .nth(1) 11 | .expect("Expects 1 argument - path to config file"); 12 | let maybe_debug: Option = args().nth(2); 13 | let mut file = File::open(cfg).expect("Failed to open cfg file"); 14 | let mut content = String::new(); 15 | file.read_to_string(&mut content) 16 | .expect("Failed to read file contents to string"); 17 | if let Some(maybe_debug) = maybe_debug { 18 | if maybe_debug == "--debug" { 19 | println!("Enabling debug."); 20 | mki::enable_debug(); 21 | } else { 22 | println!("Unknown option passed in: {}, exiting", maybe_debug) 23 | } 24 | } 25 | load_config(&content)?; 26 | thread::sleep(Duration::from_secs(u64::MAX)); 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /src/details.rs: -------------------------------------------------------------------------------- 1 | use crate::{install_hooks, process_message, Action, Event, Mouse, State}; 2 | use crate::{InhibitEvent, Keyboard}; 3 | use std::collections::HashMap; 4 | use std::fmt::Write; 5 | use std::sync::atomic::{AtomicBool, Ordering}; 6 | use std::sync::{mpsc, Arc, Mutex}; 7 | use std::thread; 8 | use std::thread::JoinHandle; 9 | use std::time::SystemTime; 10 | 11 | pub(crate) fn registry() -> &'static Registry { 12 | lazy_static::lazy_static! { 13 | static ref REGISTRY: Registry = Registry::new(); 14 | } 15 | ®ISTRY 16 | } 17 | 18 | struct Sequencer { 19 | _handle: JoinHandle<()>, 20 | 21 | tx: mpsc::Sender>, 22 | } 23 | 24 | #[derive(Default)] 25 | struct Pressed { 26 | pressed: Vec, 27 | pressed_keys: Vec, 28 | } 29 | 30 | impl Pressed { 31 | pub(crate) fn is_pressed(&self, event: Event) -> bool { 32 | self.pressed.contains(&event) 33 | } 34 | 35 | // Order matters intentionally here. 36 | pub(crate) fn are_pressed(&self, keys: &[Keyboard]) -> bool { 37 | self.pressed_keys == keys 38 | } 39 | 40 | fn pressed(&mut self, event: Event) { 41 | if !self.pressed.contains(&event) { 42 | self.pressed.push(event); 43 | 44 | if let Event::Keyboard(key) = event { 45 | self.pressed_keys.push(key); 46 | } 47 | } 48 | } 49 | 50 | fn released(&mut self, event: Event) { 51 | if let Some(index) = self.pressed.iter().position(|e| *e == event) { 52 | self.pressed.remove(index); 53 | 54 | if let Event::Keyboard(key) = event { 55 | let pos = self 56 | .pressed_keys 57 | .iter() 58 | .position(|k| *k == key) 59 | .expect("state mismtch"); 60 | self.pressed_keys.remove(pos); 61 | } 62 | } 63 | } 64 | } 65 | 66 | pub(crate) struct Registry { 67 | pub(crate) key_callbacks: Mutex>>, 68 | pub(crate) button_callbacks: Mutex>>, 69 | pub(crate) any_key_callback: Mutex>>, 70 | pub(crate) any_button_callback: Mutex>>, 71 | #[allow(clippy::type_complexity)] 72 | pub(crate) hotkeys: Mutex, Arc>>>, 73 | #[allow(clippy::type_complexity)] 74 | mouse_tracking_callback: Mutex>>>, 75 | 76 | pressed: Mutex, 77 | 78 | _handle: JoinHandle<()>, 79 | sequencer: Mutex>, 80 | 81 | state: Mutex>, 82 | 83 | pub(crate) tracking_enabled: AtomicBool, 84 | pub(crate) debug_enabled: AtomicBool, 85 | } 86 | 87 | impl Registry { 88 | pub(crate) fn new() -> Self { 89 | Registry { 90 | key_callbacks: Mutex::new(HashMap::new()), 91 | button_callbacks: Mutex::new(HashMap::new()), 92 | any_key_callback: Mutex::new(None), 93 | any_button_callback: Mutex::new(None), 94 | _handle: thread::Builder::new() 95 | .name("mki-lstn".into()) 96 | .spawn(|| { 97 | // For windows hooks need to be installed on the same thread that listens to the Messages. 98 | install_hooks(); 99 | process_message(); 100 | }) 101 | .unwrap(), 102 | sequencer: Mutex::new(None), 103 | pressed: Mutex::new(Pressed::default()), 104 | hotkeys: Mutex::new(HashMap::new()), 105 | state: Mutex::new(HashMap::new()), 106 | tracking_enabled: AtomicBool::new(false), 107 | mouse_tracking_callback: Mutex::new(None), 108 | debug_enabled: AtomicBool::new(false), 109 | } 110 | } 111 | 112 | pub(crate) fn sequence(&self, event: Event, state: State, action: Arc) { 113 | let erased_action = Box::new(move || { 114 | (action.callback)(event, state); 115 | }); 116 | let mut sequencer = self.sequencer.lock().unwrap(); 117 | let sequencer = sequencer.get_or_insert({ 118 | let (tx, rx) = mpsc::channel::>(); 119 | Sequencer { 120 | _handle: thread::Builder::new() 121 | .name("sequencer".into()) 122 | .spawn(move || { 123 | while let Ok(action) = rx.recv() { 124 | action() 125 | } 126 | }) 127 | .unwrap(), 128 | tx, 129 | } 130 | }); 131 | let _ = sequencer.tx.send(erased_action); 132 | } 133 | 134 | fn invoke_action(&self, action: Arc, event: Event, state: State) { 135 | if self.debug_enabled.load(Ordering::Relaxed) { 136 | self.maybe_log_event(&format!("invoking: {:?}", state), event); 137 | } 138 | if action.defer { 139 | thread::spawn(move || { 140 | (action.callback)(event, state); 141 | }); 142 | } else if action.sequencer { 143 | self.sequence(event, state, action) 144 | } else { 145 | (action.callback)(event, state); 146 | } 147 | } 148 | 149 | fn map_event_to_actions(&self, event: Event) -> (Option>, Option>) { 150 | let (global_action, key_action) = match event { 151 | Event::Keyboard(key) => ( 152 | self.any_key_callback.lock().unwrap().clone(), 153 | self.key_callbacks.lock().unwrap().get(&key).cloned(), 154 | ), 155 | Event::Mouse(button) => ( 156 | self.any_button_callback.lock().unwrap().clone(), 157 | self.button_callbacks.lock().unwrap().get(&button).cloned(), 158 | ), 159 | }; 160 | (global_action, key_action) 161 | } 162 | 163 | pub(crate) fn event_down(&self, event: Event) -> InhibitEvent { 164 | self.maybe_log_event("down", event); 165 | self.pressed.lock().unwrap().pressed(event); 166 | let mut callbacks = Vec::new(); 167 | if let Event::Keyboard(key) = event { 168 | for (sequence, callback) in self.hotkeys.lock().unwrap().iter() { 169 | if sequence.last() == Some(&key) 170 | && self.pressed.lock().unwrap().are_pressed(sequence) 171 | { 172 | callbacks.push(callback.clone()); 173 | } 174 | } 175 | } 176 | for callback in callbacks { 177 | // Should we not invoke actions if there is any hotkey present? 178 | thread::spawn(move || callback()); 179 | } 180 | let state = State::Pressed; 181 | let mut inhibit = InhibitEvent::No; 182 | let (global_action, key_action) = self.map_event_to_actions(event); 183 | if let Some(action) = global_action { 184 | inhibit = action.inhibit.clone(); 185 | self.invoke_action(action, event, state); 186 | } 187 | if let Some(action) = key_action { 188 | inhibit = action.inhibit.clone(); 189 | self.invoke_action(action, event, state); 190 | } 191 | 192 | inhibit 193 | } 194 | 195 | pub(crate) fn event_up(&self, event: Event) -> InhibitEvent { 196 | self.maybe_log_event("up", event); 197 | self.pressed.lock().unwrap().released(event); 198 | let state = State::Released; 199 | let (global_action, key_action) = self.map_event_to_actions(event); 200 | if let Some(action) = global_action { 201 | self.invoke_action(action, event, state); 202 | } 203 | if let Some(action) = key_action { 204 | self.invoke_action(action, event, state); 205 | } 206 | 207 | InhibitEvent::No 208 | } 209 | 210 | #[cfg(target_os = "windows")] // Not sure how to detect double on linux 211 | pub(crate) fn event_click(&self, event: Event) -> InhibitEvent { 212 | self.maybe_log_event("click", event); 213 | let inhibit = self.event_down(event); 214 | self.event_up(event); 215 | inhibit 216 | } 217 | 218 | pub(crate) fn is_pressed(&self, event: Event) -> bool { 219 | self.pressed.lock().unwrap().is_pressed(event) 220 | } 221 | 222 | pub(crate) fn are_pressed(&self, keys: &[Keyboard]) -> bool { 223 | self.pressed.lock().unwrap().are_pressed(keys) 224 | } 225 | 226 | pub(crate) fn register_hotkey( 227 | &self, 228 | sequence: &[Keyboard], 229 | handler: impl Fn() + Send + Sync + 'static, 230 | ) { 231 | let mut hotkeys = self.hotkeys.lock().unwrap(); 232 | if self.debug_enabled.load(Ordering::Relaxed) { 233 | hotkeys.insert( 234 | sequence.to_vec(), 235 | Arc::new(Box::new({ 236 | let sequence = sequence.to_vec(); 237 | move || { 238 | println!( 239 | "Invoking hotkey. sequence: {:?} ts: {:?}", 240 | sequence, 241 | log_timestamp() 242 | ); 243 | handler() 244 | } 245 | })), 246 | ); 247 | } else { 248 | hotkeys.insert(sequence.to_vec(), Arc::new(Box::new(handler))); 249 | }; 250 | } 251 | 252 | pub(crate) fn unregister_hotkey(&self, sequence: &[Keyboard]) { 253 | self.hotkeys.lock().unwrap().remove(&sequence.to_vec()); 254 | } 255 | 256 | //noinspection ALL 257 | pub fn set_state(&self, key: &str, value: &str) { 258 | self.state.lock().unwrap().insert(key.into(), value.into()); 259 | } 260 | 261 | pub fn get_state(&self, key: &str) -> Option { 262 | self.state.lock().unwrap().get(key).cloned() 263 | } 264 | 265 | pub fn is_tracking_enabled(&self) -> bool { 266 | self.tracking_enabled.load(Ordering::Relaxed) 267 | } 268 | 269 | #[allow(clippy::type_complexity)] 270 | pub fn set_mouse_tracker( 271 | &self, 272 | action: Option>>, 273 | ) { 274 | self.tracking_enabled 275 | .store(action.is_some(), Ordering::Relaxed); 276 | *self.mouse_tracking_callback.lock().unwrap() = action; 277 | } 278 | 279 | #[allow(unused)] 280 | pub(crate) fn update_mouse_position(&self, x: i32, y: i32) { 281 | if self.is_tracking_enabled() { 282 | if let Some(mouse_tracking) = self.mouse_tracking_callback.lock().unwrap().as_ref() { 283 | mouse_tracking(x, y) 284 | } 285 | } 286 | } 287 | 288 | pub fn enable_debug(&self) { 289 | self.debug_enabled.store(true, Ordering::Relaxed); 290 | } 291 | 292 | pub fn maybe_log_event(&self, prefix: &str, event: Event) { 293 | if self.debug_enabled.load(Ordering::Relaxed) { 294 | println!("Event: {} - {:?}. ts: {}", prefix, event, log_timestamp()) 295 | } 296 | } 297 | 298 | pub fn print_pressed_state(&self) { 299 | let pressed = &self.pressed.lock().unwrap(); 300 | let mut fmt = String::new(); 301 | write!(&mut fmt, "[").expect("cannot fail"); 302 | for i in 0..pressed.pressed.len() { 303 | if i != 0 { 304 | write!(&mut fmt, ", ").expect("cannot fail"); 305 | } 306 | write!(&mut fmt, "{}", pressed.pressed[i]).expect("cannot fail"); 307 | } 308 | write!(&mut fmt, "]").expect("cannot fail"); 309 | 310 | println!("Pressed Dump ts: {} - {}", log_timestamp(), fmt); 311 | } 312 | } 313 | 314 | fn log_timestamp() -> u64 { 315 | SystemTime::now() 316 | .duration_since(SystemTime::UNIX_EPOCH) 317 | .expect("<1970 ?") 318 | .as_secs() 319 | } 320 | -------------------------------------------------------------------------------- /src/keyboard.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::fmt; 3 | use std::fmt::Formatter; 4 | use std::str::FromStr; 5 | 6 | #[derive(Copy, Clone, Ord, PartialOrd, Hash, Eq, PartialEq, Debug, Serialize, Deserialize)] 7 | pub enum Keyboard { 8 | A, 9 | B, 10 | C, 11 | D, 12 | E, 13 | F, 14 | G, 15 | H, 16 | I, 17 | J, 18 | K, 19 | L, 20 | M, 21 | N, 22 | O, 23 | P, 24 | Q, 25 | R, 26 | S, 27 | T, 28 | U, 29 | V, 30 | W, 31 | X, 32 | Y, 33 | Z, 34 | Number0, 35 | Number1, 36 | Number2, 37 | Number3, 38 | Number4, 39 | Number5, 40 | Number6, 41 | Number7, 42 | Number8, 43 | Number9, 44 | LeftAlt, 45 | RightAlt, 46 | LeftShift, 47 | RightShift, 48 | LeftControl, 49 | RightControl, 50 | BackSpace, 51 | Tab, 52 | Enter, 53 | Escape, 54 | Space, 55 | PageUp, 56 | PageDown, 57 | Home, 58 | Left, 59 | Up, 60 | Right, 61 | Down, 62 | Print, 63 | PrintScreen, 64 | Insert, 65 | Delete, 66 | LeftWindows, 67 | RightWindows, 68 | Comma, // ,< 69 | Period, // .> 70 | Slash, // /? 71 | SemiColon, // ;: 72 | Apostrophe, // '" 73 | LeftBrace, // [{ 74 | BackwardSlash, // \| 75 | RightBrace, // ]} 76 | Grave, // `~ 77 | F1, 78 | F2, 79 | F3, 80 | F4, 81 | F5, 82 | F6, 83 | F7, 84 | F8, 85 | F9, 86 | F10, 87 | F11, 88 | F12, 89 | F13, 90 | F14, 91 | F15, 92 | F16, 93 | F17, 94 | F18, 95 | F19, 96 | F20, 97 | F21, 98 | F22, 99 | F23, 100 | F24, 101 | NumLock, 102 | ScrollLock, 103 | CapsLock, 104 | Numpad0, 105 | Numpad1, 106 | Numpad2, 107 | Numpad3, 108 | Numpad4, 109 | Numpad5, 110 | Numpad6, 111 | Numpad7, 112 | Numpad8, 113 | Numpad9, 114 | Multiply, 115 | Add, 116 | Separator, 117 | Subtract, 118 | Decimal, 119 | Divide, 120 | ThatThingy, 121 | Other(i32), 122 | } 123 | 124 | impl FromStr for Keyboard { 125 | type Err = (); 126 | 127 | fn from_str(s: &str) -> Result { 128 | let parsed = match s { 129 | "A" => Keyboard::A, 130 | "B" => Keyboard::B, 131 | "C" => Keyboard::C, 132 | "D" => Keyboard::D, 133 | "E" => Keyboard::E, 134 | "F" => Keyboard::F, 135 | "G" => Keyboard::G, 136 | "H" => Keyboard::H, 137 | "I" => Keyboard::I, 138 | "J" => Keyboard::J, 139 | "K" => Keyboard::K, 140 | "L" => Keyboard::L, 141 | "M" => Keyboard::M, 142 | "N" => Keyboard::N, 143 | "O" => Keyboard::O, 144 | "P" => Keyboard::P, 145 | "Q" => Keyboard::Q, 146 | "R" => Keyboard::R, 147 | "S" => Keyboard::S, 148 | "T" => Keyboard::T, 149 | "U" => Keyboard::U, 150 | "V" => Keyboard::V, 151 | "W" => Keyboard::W, 152 | "X" => Keyboard::X, 153 | "Y" => Keyboard::Y, 154 | "Z" => Keyboard::Z, 155 | "0" => Keyboard::Number0, 156 | "1" => Keyboard::Number1, 157 | "2" => Keyboard::Number2, 158 | "3" => Keyboard::Number3, 159 | "4" => Keyboard::Number4, 160 | "5" => Keyboard::Number5, 161 | "6" => Keyboard::Number6, 162 | "7" => Keyboard::Number7, 163 | "8" => Keyboard::Number8, 164 | "9" => Keyboard::Number9, 165 | "LeftAlt" => Keyboard::LeftAlt, 166 | "RightAlt" => Keyboard::RightAlt, 167 | "LeftShift" => Keyboard::LeftShift, 168 | "RightShift" => Keyboard::RightShift, 169 | "LeftControl" => Keyboard::LeftControl, 170 | "RightControl" => Keyboard::RightControl, 171 | "BackSpace" => Keyboard::BackSpace, 172 | "Tab" | " " => Keyboard::Tab, 173 | "Enter" | "\n" => Keyboard::Enter, 174 | "Escape" => Keyboard::Escape, 175 | "Space" | " " => Keyboard::Space, 176 | "PageUp" => Keyboard::PageUp, 177 | "PageDown" => Keyboard::PageDown, 178 | "Home" => Keyboard::Home, 179 | "Left" => Keyboard::Left, 180 | "Up" => Keyboard::Up, 181 | "Right" => Keyboard::Right, 182 | "Down" => Keyboard::Down, 183 | "Print" => Keyboard::Print, 184 | "PrintScreen" => Keyboard::PrintScreen, 185 | "Insert" => Keyboard::Insert, 186 | "Delete" => Keyboard::Delete, 187 | "LeftWindows" => Keyboard::LeftWindows, 188 | "RightWindows" => Keyboard::RightWindows, 189 | "Comma" | "," => Keyboard::Comma, 190 | "Period" | "." => Keyboard::Period, 191 | "Slash" | "/" | "?" => Keyboard::Slash, 192 | "SemiColon" | ";" | ":" => Keyboard::SemiColon, 193 | "Apostrophe" | "'" | "\"" => Keyboard::Apostrophe, 194 | "LeftBrace" | "[" => Keyboard::LeftBrace, 195 | "BackwardSlash" | "\\" => Keyboard::BackwardSlash, 196 | "RightBrace" | "]" => Keyboard::RightBrace, 197 | "Grave" | "`" => Keyboard::Grave, 198 | "F1" => Keyboard::F1, 199 | "F2" => Keyboard::F2, 200 | "F3" => Keyboard::F3, 201 | "F4" => Keyboard::F4, 202 | "F5" => Keyboard::F5, 203 | "F6" => Keyboard::F6, 204 | "F7" => Keyboard::F7, 205 | "F8" => Keyboard::F8, 206 | "F9" => Keyboard::F9, 207 | "F10" => Keyboard::F10, 208 | "F11" => Keyboard::F11, 209 | "F12" => Keyboard::F12, 210 | "F13" => Keyboard::F13, 211 | "F14" => Keyboard::F14, 212 | "F15" => Keyboard::F15, 213 | "F16" => Keyboard::F16, 214 | "F17" => Keyboard::F17, 215 | "F18" => Keyboard::F18, 216 | "F19" => Keyboard::F19, 217 | "F20" => Keyboard::F20, 218 | "F21" => Keyboard::F21, 219 | "F22" => Keyboard::F22, 220 | "F23" => Keyboard::F23, 221 | "F24" => Keyboard::F24, 222 | "NumLock" => Keyboard::NumLock, 223 | "ScrollLock" => Keyboard::ScrollLock, 224 | "CapsLock" => Keyboard::CapsLock, 225 | "Numpad0" => Keyboard::Numpad0, 226 | "Numpad1" => Keyboard::Numpad1, 227 | "Numpad2" => Keyboard::Numpad2, 228 | "Numpad3" => Keyboard::Numpad3, 229 | "Numpad4" => Keyboard::Numpad4, 230 | "Numpad5" => Keyboard::Numpad5, 231 | "Numpad6" => Keyboard::Numpad6, 232 | "Numpad7" => Keyboard::Numpad7, 233 | "Numpad8" => Keyboard::Numpad8, 234 | "Numpad9" => Keyboard::Numpad9, 235 | "Multiply" => Keyboard::Multiply, 236 | "Add" => Keyboard::Add, 237 | "Separator" => Keyboard::Separator, 238 | "Subtract" => Keyboard::Subtract, 239 | "Decimal" => Keyboard::Decimal, 240 | "Divide" => Keyboard::Divide, 241 | "<" | ">" => Keyboard::ThatThingy, 242 | _ => return Err(()), 243 | }; 244 | Ok(parsed) 245 | } 246 | } 247 | 248 | impl fmt::Display for Keyboard { 249 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 250 | let code: i32 = (*self).into(); 251 | f.write_fmt(format_args!("{:?}({})", self, code))?; 252 | Ok(()) 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod details; 2 | 3 | mod keyboard; 4 | #[cfg(target_os = "linux")] 5 | mod linux; 6 | mod mouse; 7 | mod parse; 8 | mod sequence; 9 | #[cfg(target_os = "windows")] 10 | mod windows; 11 | 12 | #[cfg(target_os = "linux")] 13 | use crate::linux::keyboard_mouse::kimpl; 14 | #[cfg(target_os = "linux")] 15 | use crate::linux::keyboard_mouse::mimpl; 16 | #[cfg(target_os = "windows")] 17 | use crate::windows::keyboard::kimpl; 18 | #[cfg(target_os = "windows")] 19 | use crate::windows::mouse::mimpl; 20 | pub use keyboard::*; 21 | #[cfg(target_os = "linux")] 22 | pub use linux::*; 23 | pub use mouse::*; 24 | pub use parse::load_config; 25 | pub use sequence::Sequence; 26 | #[cfg(target_os = "windows")] 27 | pub use windows::*; 28 | 29 | use crate::details::registry; 30 | use std::fmt; 31 | use std::sync::Arc; 32 | 33 | #[derive(Copy, Clone, Ord, PartialOrd, Hash, Eq, PartialEq, Debug)] 34 | /// Whether given button is now Pressed or Released. 35 | /// Send in some version of the callbacks. 36 | pub enum State { 37 | Pressed, 38 | Released, 39 | } 40 | 41 | impl Keyboard { 42 | /// Send an event to Press this key 43 | pub fn press(&self) { 44 | kimpl::press(*self) 45 | } 46 | 47 | /// Send an event to Release this key 48 | pub fn release(&self) { 49 | kimpl::release(*self) 50 | } 51 | /// Send an event to Click (Press + Release) this key 52 | pub fn click(&self) { 53 | kimpl::click(*self); 54 | } 55 | 56 | // Some buttons are toggleable like caps lock. 57 | /// Whether this KeyboardKey is toggled, applies for some buttons such as Caps Lock 58 | pub fn is_toggled(&self) -> bool { 59 | kimpl::is_toggled(*self) 60 | } 61 | 62 | /// Bind an action on this KeyboardKey, action will be invoked on a new thread. 63 | pub fn bind(&self, handler: impl Fn(Keyboard) + Send + Sync + 'static) { 64 | bind_key(*self, Action::handle_kb(handler)) 65 | } 66 | 67 | /// opposite to `bind`. Clears bind 68 | pub fn clear_bind(&self) { 69 | remove_key_bind(*self); 70 | } 71 | 72 | /// Binds an action on this KeyboardKey, a version of `bind` that can do more. 73 | pub fn act_on(&self, action: Action) { 74 | bind_key(*self, action) 75 | } 76 | 77 | /// Whether given KeyboardKey is pressed. 78 | pub fn is_pressed(&self) -> bool { 79 | registry().is_pressed(Event::Keyboard(*self)) 80 | } 81 | } 82 | 83 | impl Mouse { 84 | /// Send an event to Press this Button 85 | pub fn press(&self) { 86 | mimpl::press(*self) 87 | } 88 | 89 | /// Send an event to Release this Button 90 | pub fn click(&self) { 91 | mimpl::click(*self) 92 | } 93 | 94 | /// Send an event to Click (Press + Release) this key 95 | pub fn release(&self) { 96 | mimpl::release(*self) 97 | } 98 | 99 | // On windows this uses absolute coordinates 0;65535 see windows move_to impl note 100 | pub fn move_to(x: i32, y: i32) { 101 | mimpl::move_to(x, y) 102 | } 103 | 104 | pub fn move_by(x: i32, y: i32) { 105 | mimpl::move_by(x, y) 106 | } 107 | 108 | pub fn click_at(&self, x: i32, y: i32) { 109 | mimpl::click_at(x, y, *self); 110 | } 111 | 112 | // TODO: this does not work on linux, duh. add it. 113 | pub fn track(f: impl Fn(i32, i32) + Send + Sync + 'static) { 114 | registry().set_mouse_tracker(Some(Arc::new(Box::new(f)))); 115 | } 116 | 117 | /// Bind an action on this MouseButton, action will be invoked on a new thread. 118 | pub fn bind(&self, handler: impl Fn(Mouse) + Send + Sync + 'static) { 119 | bind_button(*self, Action::handle_mouse(handler)) 120 | } 121 | 122 | /// opposite to `bind`. Clears bind 123 | pub fn clear_bind(&self) { 124 | remove_button_bind(*self); 125 | } 126 | 127 | /// Binds an action on this MouseButton, a version of `bind` that can do more. 128 | pub fn act_on(&self, action: Action) { 129 | bind_button(*self, action) 130 | } 131 | 132 | /// Whether given MouseButton is pressed. 133 | pub fn is_pressed(&self) -> bool { 134 | registry().is_pressed(Event::Mouse(*self)) 135 | } 136 | } 137 | 138 | #[derive(Clone)] 139 | /// Works only on windows. 140 | /// Whether to propagate the event for applications down the callstack. 141 | pub enum InhibitEvent { 142 | Yes, 143 | Maybe(Arc InhibitEvent + Send + Sync>), 144 | No, 145 | } 146 | 147 | impl InhibitEvent { 148 | #[cfg(target_os = "windows")] 149 | fn should_inhibit(&self) -> bool { 150 | match self { 151 | InhibitEvent::Yes => true, 152 | InhibitEvent::Maybe(f) => { 153 | matches!(f(), InhibitEvent::Yes) 154 | } 155 | InhibitEvent::No => false, 156 | } 157 | } 158 | 159 | pub fn maybe(f: impl Fn() -> InhibitEvent + Send + Sync + 'static) -> Self { 160 | InhibitEvent::Maybe(Arc::new(f)) 161 | } 162 | } 163 | 164 | #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Debug)] 165 | pub enum Event { 166 | Keyboard(Keyboard), 167 | Mouse(Mouse), 168 | } 169 | 170 | impl fmt::Display for Event { 171 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 172 | match self { 173 | Event::Keyboard(k) => f.write_fmt(format_args!("k ({})", k)), 174 | Event::Mouse(m) => f.write_fmt(format_args!("m ({})", m)), 175 | } 176 | } 177 | } 178 | 179 | pub struct Action { 180 | /// What do you want to do on the key callback, see `defer` and `sequencer` to understand 181 | /// on which thread those are invoked. 182 | pub callback: Box, 183 | /// Whether to inhibit the event propagation to further applications down the call stack. 184 | /// This only works on windows. 185 | /// Note that for now the 'release' event cannot be inhibited. 186 | pub inhibit: InhibitEvent, 187 | /// This is the recommended mode, to 'defer' this causes every callback to be spawned on a new thread. 188 | /// On windows you cannot inject a new events from the callback invoked on the same thread 189 | /// As that would result this application to be removed from the queue. hence deferring is recommended. 190 | pub defer: bool, 191 | /// Very similar to defer but the callbacks are all sequenced in one thread. 192 | /// This is helpful if you are want to have slow tasks that should not overlap with one another. 193 | pub sequencer: bool, 194 | } 195 | 196 | impl Action { 197 | /// Helper to create probably the most common key bind. 198 | /// handler will be spawned in new thread 199 | /// will only react to key press not a release. 200 | /// Use this if you want to send inputs from the handlers as on windows it is not allowed 201 | /// to pump new events. 202 | /// will not inhibit event. 203 | pub fn handle_kb(action: impl Fn(Keyboard) + Send + Sync + 'static) -> Self { 204 | Self::handle(move |event| { 205 | if let Event::Keyboard(key) = event { 206 | action(key); 207 | } 208 | }) 209 | } 210 | 211 | /// Version of `handle_kb` but for mouse. 212 | pub fn handle_mouse(action: impl Fn(Mouse) + Send + Sync + 'static) -> Self { 213 | Self::handle(move |event| { 214 | if let Event::Mouse(button) = event { 215 | action(button); 216 | } 217 | }) 218 | } 219 | 220 | /// General version of `handle_kb` for both Mouse and Keyboard. 221 | pub fn handle(action: impl Fn(Event) + Send + Sync + 'static) -> Self { 222 | Action { 223 | callback: Box::new(move |event, state| { 224 | if state == State::Pressed { 225 | action(event) 226 | } 227 | }), 228 | inhibit: InhibitEvent::No, 229 | defer: true, 230 | sequencer: false, 231 | } 232 | } 233 | 234 | /// Helper to create callback. 235 | /// will only react to key press not a release. 236 | /// will not inhibit event. 237 | /// Use this if you want a simple handler without spawning threads. 238 | pub fn callback_kb(action: impl Fn(Keyboard) + Send + Sync + 'static) -> Self { 239 | Self::callback(move |event| { 240 | if let Event::Keyboard(key) = event { 241 | action(key); 242 | } 243 | }) 244 | } 245 | 246 | /// Version of `callback_kb` but for mouse. 247 | pub fn callback_mouse(action: impl Fn(Mouse) + Send + Sync + 'static) -> Self { 248 | Self::callback(move |event| { 249 | if let Event::Mouse(button) = event { 250 | action(button); 251 | } 252 | }) 253 | } 254 | 255 | /// General version of `callback_kb` for both Mouse and Keyboard. 256 | pub fn callback(action: impl Fn(Event) + Send + Sync + 'static) -> Self { 257 | Action { 258 | callback: Box::new(move |event, state| { 259 | if state == State::Pressed { 260 | action(event) 261 | } 262 | }), 263 | inhibit: InhibitEvent::No, 264 | defer: false, 265 | sequencer: false, 266 | } 267 | } 268 | 269 | /// Helper to create sequencing handler. 270 | /// Handler will be executed one after another in a dedicated thread 271 | /// will only react to key press not a release. 272 | /// will not inhibit event. 273 | /// Use this if you want to have complicated actions that do not overlap. 274 | pub fn sequencing_kb(action: impl Fn(Keyboard) + Send + Sync + 'static) -> Self { 275 | Self::sequencing(move |event| { 276 | if let Event::Keyboard(key) = event { 277 | action(key); 278 | } 279 | }) 280 | } 281 | 282 | /// Version of `sequencing_kb` but for mouse. 283 | pub fn sequencing_mouse(action: impl Fn(Mouse) + Send + Sync + 'static) -> Self { 284 | Self::sequencing(move |event| { 285 | if let Event::Mouse(button) = event { 286 | action(button); 287 | } 288 | }) 289 | } 290 | 291 | /// General version of `sequencing_kb` for both Mouse and Keyboard. 292 | pub fn sequencing(action: impl Fn(Event) + Send + Sync + 'static) -> Self { 293 | Action { 294 | callback: Box::new(move |event, state| { 295 | if state == State::Pressed { 296 | action(event) 297 | } 298 | }), 299 | inhibit: InhibitEvent::No, 300 | defer: false, 301 | sequencer: true, 302 | } 303 | } 304 | } 305 | 306 | /// Install any key handler that will be invoked on any key presses. 307 | /// ``` 308 | /// use mki::*; 309 | /// 310 | /// fn install_global_handler() { 311 | /// bind_any_key(Action::handle_kb(|(key)| println!("Some key pressed: {:?}", key))); 312 | /// } 313 | /// ``` 314 | pub fn bind_any_key(action: Action) { 315 | *registry().any_key_callback.lock().unwrap() = Some(Arc::new(action)) 316 | } 317 | 318 | /// Install any key handler that will be invoked on specified key presses. 319 | ///``` 320 | /// use mki::*; 321 | /// 322 | /// fn bind_some_key() { 323 | /// bind_key(Keyboard::B, Action::handle_kb(|(key)| println!("B Pressed"))); 324 | /// } 325 | /// ``` 326 | pub fn bind_key(key: Keyboard, action: Action) { 327 | registry() 328 | .key_callbacks 329 | .lock() 330 | .unwrap() 331 | .insert(key, Arc::new(action)); 332 | } 333 | 334 | /// Removes global key handler. 335 | ///``` 336 | /// use mki::*; 337 | /// 338 | /// fn remove_global_handler() { 339 | /// bind_any_key(Action::handle_kb(|(key)| println!("Some key pressed: {:?}", key))); 340 | /// remove_any_key_bind(); 341 | /// } 342 | /// ``` 343 | pub fn remove_any_key_bind() { 344 | *registry().any_key_callback.lock().unwrap() = None; 345 | } 346 | 347 | /// Removes specific key bind. 348 | pub fn remove_key_bind(key: Keyboard) { 349 | registry().key_callbacks.lock().unwrap().remove(&key); 350 | } 351 | 352 | /// Same as `bind_any_key` but for mouse buttons. 353 | pub fn bind_any_button(action: Action) { 354 | *registry().any_button_callback.lock().unwrap() = Some(Arc::new(action)) 355 | } 356 | 357 | /// Same as `bind_key` but for mouse buttons. 358 | pub fn bind_button(button: Mouse, action: Action) { 359 | registry() 360 | .button_callbacks 361 | .lock() 362 | .unwrap() 363 | .insert(button, Arc::new(action)); 364 | } 365 | 366 | /// Same as `remove_any_key_bind` but for mouse buttons. 367 | pub fn remove_any_button_bind() { 368 | *registry().any_button_callback.lock().unwrap() = None; 369 | } 370 | 371 | /// Same as `remove_key_bind` but for mouse buttons. 372 | pub fn remove_button_bind(button: Mouse) { 373 | registry().button_callbacks.lock().unwrap().remove(&button); 374 | } 375 | 376 | /// Allows for registering an action that will be triggered when sequence of buttons is pressed. 377 | /// callback will be invoked whenever last key of the sequence is pressed. 378 | /// ``` 379 | /// use mki::*; 380 | /// 381 | /// fn register() { 382 | /// register_hotkey(&[Keyboard::LeftControl, Keyboard::B], || println!("CTRL+B pressed")); 383 | /// } 384 | /// ``` 385 | pub fn register_hotkey(sequence: &[Keyboard], callback: impl Fn() + Send + Sync + 'static) { 386 | registry().register_hotkey(sequence, callback); 387 | } 388 | 389 | /// Returns whether given key sequence is currently pressed down, this may be a single key. 390 | pub fn are_pressed(sequence: &[Keyboard]) -> bool { 391 | registry().are_pressed(sequence) 392 | } 393 | 394 | /// Allows storing some kind of state within the library, 395 | /// Generally not that useful but allows for some more complicated logic using yaml load. 396 | pub fn set_state(key: &str, value: &str) { 397 | registry().set_state(key, value) 398 | } 399 | 400 | /// Returns the state, it has to be set beforehand with the set otherwise will be returned empty. 401 | pub fn get_state(key: &str) -> Option { 402 | registry().get_state(key) 403 | } 404 | 405 | /// Unregisters hotkey, a original sequence has to be passed as parameter.. 406 | pub fn unregister_hotkey(sequence: &[Keyboard]) { 407 | registry().unregister_hotkey(sequence); 408 | } 409 | 410 | pub fn enable_debug() { 411 | registry().enable_debug(); 412 | } 413 | 414 | pub fn print_pressed_state() { 415 | registry().print_pressed_state(); 416 | } 417 | -------------------------------------------------------------------------------- /src/linux/keyboard_mouse.rs: -------------------------------------------------------------------------------- 1 | use crate::{Keyboard, Mouse}; 2 | use std::ptr; 3 | use std::sync::atomic::{AtomicPtr, Ordering}; 4 | use std::sync::{Arc, Mutex, MutexGuard}; 5 | use std::time::Duration; 6 | use uinput::event::keyboard::Key; 7 | use uinput::event::Code; 8 | use x11::xlib; 9 | 10 | enum KeybdAction { 11 | Press, 12 | Release, 13 | Click, 14 | } 15 | 16 | pub(crate) mod kimpl { 17 | use crate::keyboard_mouse::{send_key_stroke, with_display, KeybdAction}; 18 | use crate::Keyboard; 19 | use std::mem::MaybeUninit; 20 | use x11::xlib; 21 | 22 | pub(crate) fn press(key: Keyboard) { 23 | send_key_stroke(KeybdAction::Press, key) 24 | } 25 | 26 | pub(crate) fn release(key: Keyboard) { 27 | send_key_stroke(KeybdAction::Release, key) 28 | } 29 | 30 | pub(crate) fn click(key: Keyboard) { 31 | send_key_stroke(KeybdAction::Click, key) 32 | } 33 | 34 | pub(crate) fn is_toggled(key: Keyboard) -> bool { 35 | if let Some(key) = match key { 36 | Keyboard::ScrollLock => Some(4), 37 | Keyboard::NumLock => Some(2), 38 | Keyboard::CapsLock => Some(1), 39 | _ => None, 40 | } { 41 | let mut state: xlib::XKeyboardState = unsafe { MaybeUninit::zeroed().assume_init() }; 42 | with_display(|display| unsafe { 43 | xlib::XGetKeyboardControl(display, &mut state); 44 | }); 45 | state.led_mask & key != 0 46 | } else { 47 | false 48 | } 49 | } 50 | } 51 | 52 | fn send_key_stroke(action: KeybdAction, key: Keyboard) { 53 | let mut device = device(); 54 | if let Some(key) = key_to_event(key) { 55 | match action { 56 | KeybdAction::Press => device.press(&key).unwrap(), 57 | KeybdAction::Release => device.release(&key).unwrap(), 58 | KeybdAction::Click => device.click(&key).unwrap(), 59 | } 60 | } 61 | device.synchronize().unwrap(); 62 | } 63 | 64 | fn device() -> MutexGuard<'static, uinput::Device> { 65 | lazy_static::lazy_static! { 66 | static ref DEVICE: Arc> = { 67 | let mut device = 68 | uinput::default() 69 | .unwrap() 70 | .name("mki") 71 | .unwrap() 72 | .event(uinput::event::Keyboard::All) 73 | .unwrap(); 74 | for v in uinput::event::controller::Mouse::iter_variants() { 75 | device = device.event(v).unwrap(); 76 | } 77 | // This does not seem to work. 78 | // device = device.event(Event::Absolute(Absolute::Position(Position::X))).unwrap().min(0).max(100); 79 | // device = device.event(Event::Absolute(Absolute::Position(Position::Y))).unwrap().min(0).max(100); 80 | let mut device = device.create().unwrap(); 81 | // Without this there seems to be some inputs gone to hell 82 | device.synchronize().unwrap(); 83 | std::thread::sleep(Duration::from_millis(100)); 84 | Arc::new(Mutex::new(device)) 85 | }; 86 | } 87 | DEVICE.lock().unwrap() 88 | } 89 | 90 | fn with_display(mut f: impl FnMut(*mut xlib::Display) -> R) -> R { 91 | lazy_static::lazy_static! { 92 | static ref DISPLAY: Arc>> = { 93 | unsafe {xlib::XInitThreads()}; 94 | let display = unsafe { xlib::XOpenDisplay(ptr::null()) }; 95 | Arc::new(Mutex::new(AtomicPtr::new(display))) 96 | }; 97 | } 98 | let locked = DISPLAY.lock().unwrap(); 99 | let display: *mut xlib::Display = locked.load(Ordering::Relaxed); 100 | unsafe { xlib::XLockDisplay(display) } 101 | let r = f(display); 102 | unsafe { 103 | xlib::XFlush(display); 104 | xlib::XUnlockDisplay(display); 105 | } 106 | r 107 | } 108 | 109 | pub fn key_to_event(key: Keyboard) -> Option { 110 | use Keyboard::*; 111 | match key { 112 | BackSpace => Some(Key::BackSpace), 113 | Tab => Some(Key::Tab), 114 | Enter => Some(Key::Enter), 115 | Escape => Some(Key::Esc), 116 | Space => Some(Key::Space), 117 | Home => Some(Key::Home), 118 | Left => Some(Key::Left), 119 | Up => Some(Key::Up), 120 | Right => Some(Key::Right), 121 | Down => Some(Key::Down), 122 | Insert => Some(Key::Insert), 123 | Delete => Some(Key::Delete), 124 | Number0 => Some(Key::_0), 125 | Number1 => Some(Key::_1), 126 | Number2 => Some(Key::_2), 127 | Number3 => Some(Key::_3), 128 | Number4 => Some(Key::_4), 129 | Number5 => Some(Key::_5), 130 | Number6 => Some(Key::_6), 131 | Number7 => Some(Key::_7), 132 | Number8 => Some(Key::_8), 133 | Number9 => Some(Key::_9), 134 | A => Some(Key::A), 135 | B => Some(Key::B), 136 | C => Some(Key::C), 137 | D => Some(Key::D), 138 | E => Some(Key::E), 139 | F => Some(Key::F), 140 | G => Some(Key::G), 141 | H => Some(Key::H), 142 | I => Some(Key::I), 143 | J => Some(Key::J), 144 | K => Some(Key::K), 145 | L => Some(Key::L), 146 | M => Some(Key::M), 147 | N => Some(Key::N), 148 | O => Some(Key::O), 149 | P => Some(Key::P), 150 | Q => Some(Key::Q), 151 | R => Some(Key::R), 152 | S => Some(Key::S), 153 | T => Some(Key::T), 154 | U => Some(Key::U), 155 | V => Some(Key::V), 156 | W => Some(Key::W), 157 | X => Some(Key::X), 158 | Y => Some(Key::Y), 159 | Z => Some(Key::Z), 160 | Numpad0 => Some(Key::_0), 161 | Numpad1 => Some(Key::_1), 162 | Numpad2 => Some(Key::_2), 163 | Numpad3 => Some(Key::_3), 164 | Numpad4 => Some(Key::_4), 165 | Numpad5 => Some(Key::_5), 166 | Numpad6 => Some(Key::_6), 167 | Numpad7 => Some(Key::_7), 168 | Numpad8 => Some(Key::_8), 169 | Numpad9 => Some(Key::_9), 170 | F1 => Some(Key::F1), 171 | F2 => Some(Key::F2), 172 | F3 => Some(Key::F3), 173 | F4 => Some(Key::F4), 174 | F5 => Some(Key::F5), 175 | F6 => Some(Key::F6), 176 | F7 => Some(Key::F7), 177 | F8 => Some(Key::F8), 178 | F9 => Some(Key::F9), 179 | F10 => Some(Key::F10), 180 | NumLock => Some(Key::NumLock), 181 | ScrollLock => Some(Key::ScrollLock), 182 | CapsLock => Some(Key::CapsLock), 183 | LeftShift => Some(Key::LeftShift), 184 | RightShift => Some(Key::RightShift), 185 | LeftControl => Some(Key::LeftControl), 186 | F11 => Some(Key::F11), 187 | F12 => Some(Key::F12), 188 | F13 => Some(Key::F13), 189 | F14 => Some(Key::F14), 190 | F15 => Some(Key::F15), 191 | F16 => Some(Key::F16), 192 | F17 => Some(Key::F17), 193 | F18 => Some(Key::F18), 194 | F19 => Some(Key::F19), 195 | F20 => Some(Key::F20), 196 | F21 => Some(Key::F21), 197 | F22 => Some(Key::F22), 198 | F23 => Some(Key::F23), 199 | F24 => Some(Key::F24), 200 | RightControl => Some(Key::RightControl), 201 | Other(_code) => None, 202 | LeftAlt => Some(Key::LeftAlt), 203 | RightAlt => Some(Key::RightAlt), 204 | PageUp => Some(Key::PageUp), 205 | PageDown => Some(Key::PageDown), 206 | Print => None, 207 | PrintScreen => None, 208 | LeftWindows => None, 209 | RightWindows => None, 210 | Multiply => None, 211 | Add => None, 212 | Separator => None, 213 | Subtract => None, 214 | Decimal => None, 215 | Divide => None, 216 | Comma => Some(Key::Comma), 217 | Period => Some(Key::Dot), 218 | Slash => Some(Key::Slash), 219 | SemiColon => Some(Key::SemiColon), 220 | Apostrophe => Some(Key::Apostrophe), 221 | LeftBrace => Some(Key::LeftBrace), 222 | BackwardSlash => Some(Key::BackSlash), 223 | RightBrace => Some(Key::RightBrace), 224 | Grave => Some(Key::Grave), 225 | } 226 | } 227 | 228 | impl From for i32 { 229 | fn from(key: Keyboard) -> i32 { 230 | if let Some(key) = key_to_event(key) { 231 | key.code() 232 | } else { 233 | -1 234 | } 235 | } 236 | } 237 | 238 | pub(crate) fn kb_code_to_key(code: u32) -> Keyboard { 239 | use Keyboard::*; 240 | match code as i32 { 241 | code if Key::BackSpace.code() == code => BackSpace, 242 | code if Key::Tab.code() == code => Tab, 243 | code if Key::Enter.code() == code => Enter, 244 | code if Key::Esc.code() == code => Escape, 245 | code if Key::Space.code() == code => Space, 246 | code if Key::Home.code() == code => Home, 247 | code if Key::Left.code() == code => Left, 248 | code if Key::Up.code() == code => Up, 249 | code if Key::Right.code() == code => Right, 250 | code if Key::Down.code() == code => Down, 251 | code if Key::Insert.code() == code => Insert, 252 | code if Key::Delete.code() == code => Delete, 253 | code if Key::_0.code() == code => Number0, 254 | code if Key::_1.code() == code => Number1, 255 | code if Key::_2.code() == code => Number2, 256 | code if Key::_3.code() == code => Number3, 257 | code if Key::_4.code() == code => Number4, 258 | code if Key::_5.code() == code => Number5, 259 | code if Key::_6.code() == code => Number6, 260 | code if Key::_7.code() == code => Number7, 261 | code if Key::_8.code() == code => Number8, 262 | code if Key::_9.code() == code => Number9, 263 | code if Key::A.code() == code => A, 264 | code if Key::B.code() == code => B, 265 | code if Key::C.code() == code => C, 266 | code if Key::D.code() == code => D, 267 | code if Key::E.code() == code => E, 268 | code if Key::F.code() == code => F, 269 | code if Key::G.code() == code => G, 270 | code if Key::H.code() == code => H, 271 | code if Key::I.code() == code => I, 272 | code if Key::J.code() == code => J, 273 | code if Key::K.code() == code => K, 274 | code if Key::L.code() == code => L, 275 | code if Key::M.code() == code => M, 276 | code if Key::N.code() == code => N, 277 | code if Key::O.code() == code => O, 278 | code if Key::P.code() == code => P, 279 | code if Key::Q.code() == code => Q, 280 | code if Key::R.code() == code => R, 281 | code if Key::S.code() == code => S, 282 | code if Key::T.code() == code => T, 283 | code if Key::U.code() == code => U, 284 | code if Key::V.code() == code => V, 285 | code if Key::W.code() == code => W, 286 | code if Key::X.code() == code => X, 287 | code if Key::Y.code() == code => Y, 288 | code if Key::Z.code() == code => Z, 289 | code if Key::_0.code() == code => Numpad0, 290 | code if Key::_1.code() == code => Numpad1, 291 | code if Key::_2.code() == code => Numpad2, 292 | code if Key::_3.code() == code => Numpad3, 293 | code if Key::_4.code() == code => Numpad4, 294 | code if Key::_5.code() == code => Numpad5, 295 | code if Key::_6.code() == code => Numpad6, 296 | code if Key::_7.code() == code => Numpad7, 297 | code if Key::_8.code() == code => Numpad8, 298 | code if Key::_9.code() == code => Numpad9, 299 | code if Key::F1.code() == code => F1, 300 | code if Key::F2.code() == code => F2, 301 | code if Key::F3.code() == code => F3, 302 | code if Key::F4.code() == code => F4, 303 | code if Key::F5.code() == code => F5, 304 | code if Key::F6.code() == code => F6, 305 | code if Key::F7.code() == code => F7, 306 | code if Key::F8.code() == code => F8, 307 | code if Key::F9.code() == code => F9, 308 | code if Key::F10.code() == code => F10, 309 | code if Key::NumLock.code() == code => NumLock, 310 | code if Key::ScrollLock.code() == code => ScrollLock, 311 | code if Key::CapsLock.code() == code => CapsLock, 312 | code if Key::LeftShift.code() == code => LeftShift, 313 | code if Key::RightShift.code() == code => RightShift, 314 | code if Key::LeftControl.code() == code => LeftControl, 315 | code if Key::F11.code() == code => F11, 316 | code if Key::F12.code() == code => F12, 317 | code if Key::F13.code() == code => F13, 318 | code if Key::F14.code() == code => F14, 319 | code if Key::F15.code() == code => F15, 320 | code if Key::F16.code() == code => F16, 321 | code if Key::F17.code() == code => F17, 322 | code if Key::F18.code() == code => F18, 323 | code if Key::F19.code() == code => F19, 324 | code if Key::F20.code() == code => F20, 325 | code if Key::F21.code() == code => F21, 326 | code if Key::F22.code() == code => F22, 327 | code if Key::F23.code() == code => F23, 328 | code if Key::F24.code() == code => F24, 329 | code if Key::RightControl.code() == code => RightControl, 330 | code if Key::PageUp.code() == code => PageUp, 331 | code if Key::PageDown.code() == code => PageDown, 332 | code if Key::Comma.code() == code => Comma, 333 | code if Key::Dot.code() == code => Period, 334 | code if Key::Slash.code() == code => Slash, 335 | code if Key::SemiColon.code() == code => SemiColon, 336 | code if Key::Apostrophe.code() == code => Apostrophe, 337 | code if Key::LeftBrace.code() == code => LeftBrace, 338 | code if Key::BackSlash.code() == code => BackwardSlash, 339 | code if Key::RightBrace.code() == code => RightBrace, 340 | code if Key::Grave.code() == code => Grave, 341 | code => Other(code), 342 | // Print, PrintScreen, LeftWin, RightWin, Add, Subtract, Multiply, Divide, Separator, Subtract 343 | // Decimal Divide 344 | } 345 | } 346 | 347 | pub(crate) fn mouse_code_to_key(code: u32) -> Option { 348 | use uinput::event::controller::Mouse as IMouse; 349 | Some(match code as i32 { 350 | code if IMouse::Left.code() == code => Mouse::Left, 351 | code if IMouse::Right.code() == code => Mouse::Right, 352 | code if IMouse::Middle.code() == code => Mouse::Middle, 353 | code if IMouse::Side.code() == code => Mouse::Side, 354 | code if IMouse::Extra.code() == code => Mouse::Extra, 355 | code if IMouse::Forward.code() == code => Mouse::Forward, 356 | code if IMouse::Back.code() == code => Mouse::Back, 357 | code if IMouse::Task.code() == code => Mouse::Task, 358 | _ => return None, 359 | }) 360 | } 361 | 362 | fn mouse_to_xlib_code(mouse: Mouse) -> Option { 363 | let mapped = match mouse { 364 | Mouse::Left => 1, 365 | Mouse::Right => 3, 366 | Mouse::Middle => 2, 367 | Mouse::Side => 4, 368 | Mouse::Extra => 5, 369 | Mouse::Forward | Mouse::Back | Mouse::Task => return None, 370 | }; 371 | Some(mapped) 372 | } 373 | 374 | pub(crate) mod mimpl { 375 | use crate::keyboard_mouse::{mouse_to_xlib_code, with_display}; 376 | use crate::Mouse; 377 | use x11::xlib::{XDefaultScreen, XRootWindow, XWarpPointer}; 378 | use x11::xtest; 379 | 380 | pub(crate) fn press(button: Mouse) { 381 | if let Some(code) = mouse_to_xlib_code(button) { 382 | with_display(|display| { 383 | unsafe { xtest::XTestFakeButtonEvent(display, code, 1, 0) }; 384 | }); 385 | } 386 | } 387 | 388 | pub(crate) fn click(button: Mouse) { 389 | press(button); 390 | release(button); 391 | } 392 | 393 | pub(crate) fn release(button: Mouse) { 394 | if let Some(code) = mouse_to_xlib_code(button) { 395 | with_display(|display| { 396 | unsafe { xtest::XTestFakeButtonEvent(display, code, 0, 0) }; 397 | }); 398 | } 399 | } 400 | 401 | pub(crate) fn move_to(x: i32, y: i32) { 402 | with_display(|display| unsafe { 403 | XWarpPointer( 404 | display, 405 | 0, 406 | XRootWindow(display, XDefaultScreen(display)), 407 | 0, 408 | 0, 409 | 0, 410 | 0, 411 | x, 412 | y, 413 | ); 414 | }); 415 | } 416 | 417 | pub(crate) fn move_by(x: i32, y: i32) { 418 | with_display(|display| unsafe { 419 | XWarpPointer(display, 0, 0, 0, 0, 0, 0, x, y); 420 | }); 421 | } 422 | 423 | pub(crate) fn click_at(x: i32, y: i32, button: Mouse) { 424 | move_to(x, y); 425 | click(button); 426 | } 427 | } 428 | -------------------------------------------------------------------------------- /src/linux/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod keyboard_mouse; 2 | 3 | use crate::details::registry; 4 | use crate::keyboard_mouse::{kb_code_to_key, mouse_code_to_key}; 5 | use crate::Event; 6 | use input::event::keyboard::{KeyState, KeyboardEventTrait}; 7 | use input::event::pointer::ButtonState; 8 | use input::event::pointer::PointerEvent::Button; 9 | use input::{Libinput, LibinputInterface}; 10 | use nix::fcntl::{open, OFlag}; 11 | use nix::poll::{poll, PollFd, PollFlags}; 12 | use nix::sys::stat::Mode; 13 | use nix::unistd::close; 14 | use std::os::unix::io::AsRawFd; 15 | use std::os::unix::io::RawFd; 16 | use std::path::Path; 17 | 18 | pub(crate) fn install_hooks() {} 19 | 20 | pub(crate) fn process_message() { 21 | struct LibinputInterfaceRaw; 22 | 23 | impl LibinputInterface for LibinputInterfaceRaw { 24 | fn open_restricted(&mut self, path: &Path, flags: i32) -> std::result::Result { 25 | if let Ok(fd) = open(path, OFlag::from_bits_truncate(flags), Mode::empty()) { 26 | Ok(fd) 27 | } else { 28 | Err(1) 29 | } 30 | } 31 | 32 | fn close_restricted(&mut self, fd: RawFd) { 33 | let _ = close(fd); 34 | } 35 | } 36 | let mut libinput = Libinput::new_with_udev(LibinputInterfaceRaw); 37 | libinput.udev_assign_seat("seat0").unwrap(); 38 | let pollfd = PollFd::new(libinput.as_raw_fd(), PollFlags::POLLIN); 39 | while poll(&mut [pollfd], -1).is_ok() { 40 | libinput.dispatch().unwrap(); 41 | #[allow(clippy::while_let_on_iterator)] 42 | while let Some(event) = libinput.next() { 43 | handle_libinput_event(event); 44 | } 45 | } 46 | } 47 | 48 | fn handle_libinput_event(event: input::Event) { 49 | match event { 50 | input::Event::Device(_) => {} 51 | input::Event::Keyboard(kb) => { 52 | let key = kb_code_to_key(kb.key()); 53 | match kb.key_state() { 54 | KeyState::Pressed => { 55 | registry().event_down(Event::Keyboard(key)); 56 | } 57 | KeyState::Released => { 58 | registry().event_up(Event::Keyboard(key)); 59 | } 60 | } 61 | } 62 | input::Event::Pointer(Button(button_event)) => { 63 | if let Some(mapped) = mouse_code_to_key(button_event.button()) { 64 | match button_event.button_state() { 65 | ButtonState::Pressed => { 66 | registry().event_down(Event::Mouse(mapped)); 67 | } 68 | ButtonState::Released => { 69 | registry().event_up(Event::Mouse(mapped)); 70 | } 71 | } 72 | } 73 | } 74 | input::Event::Pointer(_) => { 75 | // This one gets invoked but it seems pretty useless 76 | // if let Motion(motion_event) = &pointer { 77 | // println!("1 {} {}", motion_event.dx(), motion_event.dy()); 78 | // } 79 | // if let MotionAbsolute(motion_event) = &pointer { 80 | // Unfortunatelly this one does not get invoked? 81 | // } 82 | } 83 | input::Event::Touch(_) => { /*println!("touch")*/ } 84 | input::Event::Tablet(_) => { /*println!("touch2")*/ } 85 | input::Event::TabletPad(_) => { /*println!("touch3")*/ } 86 | input::Event::Gesture(_) => { /*println!("touch4")*/ } 87 | input::Event::Switch(_) => { /*println!("touch5")*/ } 88 | _other => { /*println!("A different event={}", _other)*/ } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/mouse.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::fmt; 3 | 4 | #[cfg(target_os = "windows")] // Not sure how to detect double on linux 5 | #[derive(Copy, Clone, Ord, PartialOrd, Hash, Eq, PartialEq, Debug, Serialize, Deserialize)] 6 | pub enum Mouse { 7 | Left, 8 | DoubleLeft, 9 | Right, 10 | DoubleRight, 11 | Middle, 12 | DoubleMiddle, 13 | Side, // XBUTTON1 14 | DoubleSide, 15 | Extra, // XBUTTON2 16 | DoubleExtra, 17 | } 18 | 19 | #[cfg(target_os = "linux")] 20 | #[derive(Copy, Clone, Ord, PartialOrd, Hash, Eq, PartialEq, Debug, Serialize, Deserialize)] 21 | pub enum Mouse { 22 | Left, 23 | Right, 24 | Middle, 25 | Side, 26 | Extra, 27 | Forward, 28 | Back, 29 | Task, 30 | } 31 | 32 | impl fmt::Display for Mouse { 33 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 34 | f.write_fmt(format_args!("{:?}", self)) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/parse.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | are_pressed, get_state, print_pressed_state, register_hotkey, set_state, Keyboard, Mouse, 3 | }; 4 | use serde::de::Error; 5 | use serde::{Deserialize, Serialize}; 6 | use std::thread; 7 | use std::time::Duration; 8 | 9 | #[derive(Deserialize, Serialize)] 10 | struct Config { 11 | bind: Vec, 12 | } 13 | 14 | #[derive(Deserialize, Serialize)] 15 | struct Bind { 16 | #[serde(skip_serializing_if = "Option::is_none")] 17 | description: Option, 18 | #[serde(flatten)] 19 | input: Input, 20 | 21 | action: Action, 22 | } 23 | 24 | #[derive(Deserialize, Serialize)] 25 | struct Input { 26 | #[serde(skip_serializing_if = "Option::is_none")] 27 | key: Option>, 28 | #[serde(skip_serializing_if = "Option::is_none")] 29 | button: Option>, 30 | } 31 | 32 | impl Input { 33 | fn validate(&self) -> Result<(), serde_yaml::Error> { 34 | if self.key.is_none() && self.button.is_none() { 35 | Err(serde_yaml::Error::custom("Bind had neither key nor button")) 36 | } else if self.key.is_some() && self.button.is_some() { 37 | Err(serde_yaml::Error::custom("Bind had both key and button")) 38 | } else if let Some(keys) = self.key.as_ref() { 39 | if keys.is_empty() { 40 | Err(serde_yaml::Error::custom("Bind had empty keys")) 41 | } else { 42 | Ok(()) 43 | } 44 | } else if let Some(buttons) = self.button.as_ref() { 45 | if buttons.is_empty() { 46 | Err(serde_yaml::Error::custom("Bind had empty buttons")) 47 | } else if buttons.len() != 1 { 48 | Err(serde_yaml::Error::custom( 49 | "Only single mouse button hotkey currently supported", 50 | )) 51 | } else { 52 | Ok(()) 53 | } 54 | } else { 55 | Ok(()) 56 | } 57 | } 58 | #[allow(unused)] 59 | fn key(key: Keyboard) -> Self { 60 | Input { 61 | button: None, 62 | key: Some(vec![key]), 63 | } 64 | } 65 | } 66 | 67 | #[derive(Deserialize, Serialize)] 68 | struct Pressed { 69 | input: Input, 70 | action: Vec, 71 | } 72 | 73 | #[derive(Deserialize, Serialize)] 74 | struct SetState { 75 | name: String, 76 | value: String, 77 | } 78 | 79 | #[derive(Deserialize, Serialize)] 80 | struct StateMatches { 81 | name: String, 82 | value: String, 83 | action: Vec, 84 | } 85 | 86 | #[derive(Deserialize, Serialize)] 87 | #[serde(rename_all = "kebab-case")] 88 | enum Action { 89 | Multi(Vec), 90 | Pressed(Pressed), 91 | StateMatches(StateMatches), 92 | WhileStateMatches(StateMatches), 93 | Press(Input), 94 | Release(Input), 95 | Click(Input), 96 | Sleep(u64), // Milliseconds 97 | SetState(SetState), 98 | Println(String), 99 | PrintState(String), 100 | PrintPressedState, 101 | } 102 | 103 | fn validate_actions(actions: &[Action]) -> serde_yaml::Result<()> { 104 | for a in actions { 105 | validate_action(a)?; 106 | } 107 | Ok(()) 108 | } 109 | 110 | fn validate_action(action: &Action) -> serde_yaml::Result<()> { 111 | match action { 112 | Action::Multi(actions) => { 113 | validate_actions(actions)?; 114 | } 115 | Action::Pressed(pressed) => { 116 | pressed.input.validate()?; 117 | if pressed.input.key.is_none() { 118 | return Err(serde_yaml::Error::custom("Pressed can only check keys.")); 119 | } 120 | validate_actions(&pressed.action)?; 121 | } 122 | Action::StateMatches(state_matches) => { 123 | validate_actions(&state_matches.action)?; 124 | } 125 | Action::WhileStateMatches(state_matches) => { 126 | validate_actions(&state_matches.action)?; 127 | } 128 | Action::Press(_) 129 | | Action::Release(_) 130 | | Action::Click(_) 131 | | Action::Sleep(_) 132 | | Action::SetState(_) 133 | | Action::Println(_) 134 | | Action::PrintState(_) => {} 135 | Action::PrintPressedState => {} 136 | } 137 | 138 | Ok(()) 139 | } 140 | 141 | fn handle_actions(actions: &[Action]) { 142 | for a in actions { 143 | handle_action(a); 144 | } 145 | } 146 | 147 | fn handle_action(action: &Action) { 148 | match action { 149 | Action::Multi(actions) => { 150 | handle_actions(actions); 151 | } 152 | Action::Pressed(pressed) => { 153 | let keys = pressed.input.key.as_ref().unwrap(); 154 | if are_pressed(keys) { 155 | handle_actions(&pressed.action); 156 | } 157 | } 158 | Action::StateMatches(state_matches) => { 159 | if let Some(state) = get_state(&state_matches.name) { 160 | if state == state_matches.value { 161 | handle_actions(&state_matches.action); 162 | } 163 | } 164 | } 165 | Action::WhileStateMatches(state_matches) => { 166 | while let Some(state) = get_state(&state_matches.name) { 167 | if state == state_matches.value { 168 | handle_actions(&state_matches.action); 169 | } else { 170 | break; 171 | } 172 | } 173 | } 174 | Action::Press(input) => { 175 | if let Some(keys) = &input.key { 176 | for k in keys { 177 | k.press(); 178 | } 179 | } 180 | if let Some(buttons) = &input.button { 181 | for b in buttons { 182 | b.press(); 183 | } 184 | } 185 | } 186 | Action::Release(input) => { 187 | if let Some(keys) = &input.key { 188 | for k in keys { 189 | k.release(); 190 | } 191 | } 192 | if let Some(buttons) = &input.button { 193 | for b in buttons { 194 | b.release(); 195 | } 196 | } 197 | } 198 | Action::Click(input) => { 199 | if let Some(keys) = &input.key { 200 | for k in keys { 201 | k.click() 202 | } 203 | } 204 | if let Some(buttons) = &input.button { 205 | for b in buttons { 206 | b.click() 207 | } 208 | } 209 | } 210 | Action::Sleep(millis) => { 211 | thread::sleep(Duration::from_millis(*millis)); 212 | } 213 | Action::SetState(state) => set_state(&state.name, &state.value), 214 | Action::Println(message) => { 215 | println!("{}", message); 216 | } 217 | Action::PrintState(state) => { 218 | println!("State under key: {} is: {:?}", state, get_state(state)) 219 | } 220 | Action::PrintPressedState => { 221 | print_pressed_state(); 222 | } 223 | } 224 | } 225 | 226 | pub fn load_config(content: &str) -> Result<(), serde_yaml::Error> { 227 | let config: Config = serde_yaml::from_str(content)?; 228 | for bind in config.bind { 229 | bind.input.validate()?; 230 | let action = bind.action; 231 | validate_action(&action)?; 232 | match (bind.input.key, bind.input.button) { 233 | (Some(keys), None) => { 234 | println!("Now binding a hotkey for: {:?}", keys); 235 | if let Some(description) = bind.description { 236 | println!("description: {}", description); 237 | } 238 | register_hotkey(&keys, move || { 239 | handle_action(&action); 240 | }) 241 | } 242 | (None, Some(buttons)) => { 243 | if buttons.len() != 1 { 244 | panic!( 245 | "Mouse combination cannot be bound, single mouse expected: {:?}", 246 | buttons 247 | ); 248 | } 249 | let button = buttons[0]; 250 | button.bind(move |_mouse_why_is_this_here| { 251 | handle_action(&action); 252 | }) 253 | } 254 | _ => { 255 | unreachable!("Checked in validate_action"); 256 | } 257 | } 258 | } 259 | Ok(()) 260 | } 261 | 262 | #[cfg(test)] 263 | mod tests { 264 | use crate::parse::{Action, Bind, Config, Input, SetState, StateMatches}; 265 | use crate::Keyboard::{LeftControl, Number0, Number1, D, E, H, K, L, R, S, W}; 266 | 267 | #[test] 268 | fn example() { 269 | let c = Config { 270 | bind: vec![ 271 | Bind { 272 | description: Some( 273 | "LCtrl + H: [Loop until state is 1 [printing W, Sleep100]], then print E" 274 | .into(), 275 | ), 276 | input: Input { 277 | key: Some(vec![LeftControl, H]), 278 | button: None, 279 | }, 280 | action: Action::Multi(vec![ 281 | Action::WhileStateMatches(StateMatches { 282 | name: "test".into(), 283 | value: "1".into(), 284 | action: vec![Action::Click(Input::key(W)), Action::Sleep(100)], 285 | }), 286 | Action::Click(Input::key(E)), 287 | ]), 288 | }, 289 | Bind { 290 | description: Some("S: Set state to 1 then print it".into()), 291 | input: Input::key(S), 292 | action: Action::Multi(vec![ 293 | Action::SetState(SetState { 294 | name: "test".into(), 295 | value: "1".into(), 296 | }), 297 | Action::PrintState("test".into()), 298 | ]), 299 | }, 300 | Bind { 301 | description: Some("R: Set state to 0 then print it".into()), 302 | input: Input::key(R), 303 | action: Action::Multi(vec![ 304 | Action::SetState(SetState { 305 | name: "test".into(), 306 | value: "0".into(), 307 | }), 308 | Action::PrintState("test".into()), 309 | ]), 310 | }, 311 | Bind { 312 | description: Some("If state 1 then click 1; If state 0 then click 0".into()), 313 | input: Input::key(D), 314 | action: Action::Multi(vec![ 315 | Action::StateMatches(StateMatches { 316 | name: "test".into(), 317 | value: "1".into(), 318 | action: vec![Action::Click(Input::key(Number1))], 319 | }), 320 | Action::StateMatches(StateMatches { 321 | name: "test".into(), 322 | value: "0".into(), 323 | action: vec![Action::Click(Input::key(Number0))], 324 | }), 325 | ]), 326 | }, 327 | ], 328 | }; 329 | assert_eq!( 330 | r#"--- 331 | bind: 332 | - description: "LCtrl + H: [Loop until state is 1 [printing W, Sleep100]], then print E" 333 | key: 334 | - LeftControl 335 | - H 336 | action: 337 | multi: 338 | - while-state-matches: 339 | name: test 340 | value: "1" 341 | action: 342 | - click: 343 | key: 344 | - W 345 | - sleep: 100 346 | - click: 347 | key: 348 | - E 349 | - description: "S: Set state to 1 then print it" 350 | key: 351 | - S 352 | action: 353 | multi: 354 | - set-state: 355 | name: test 356 | value: "1" 357 | - print-state: test 358 | - description: "R: Set state to 0 then print it" 359 | key: 360 | - R 361 | action: 362 | multi: 363 | - set-state: 364 | name: test 365 | value: "0" 366 | - print-state: test 367 | - description: If state 1 then click 1; If state 0 then click 0 368 | key: 369 | - D 370 | action: 371 | multi: 372 | - state-matches: 373 | name: test 374 | value: "1" 375 | action: 376 | - click: 377 | key: 378 | - Number1 379 | - state-matches: 380 | name: test 381 | value: "0" 382 | action: 383 | - click: 384 | key: 385 | - Number0 386 | "#, 387 | serde_yaml::to_string(&c).unwrap() 388 | ) 389 | } 390 | 391 | #[test] 392 | fn readme_example() { 393 | let c = Config { 394 | bind: vec![Bind { 395 | description: Some("Whenever Ctrl+L is clicked click K as well".into()), 396 | input: Input { 397 | key: Some(vec![LeftControl, L]), 398 | button: None, 399 | }, 400 | action: Action::Click(Input::key(K)), 401 | }], 402 | }; 403 | assert_eq!( 404 | r#"--- 405 | bind: 406 | - description: Whenever Ctrl+L is clicked click K as well 407 | key: 408 | - LeftControl 409 | - L 410 | action: 411 | click: 412 | key: 413 | - K 414 | "#, 415 | serde_yaml::to_string(&c).unwrap() 416 | ); 417 | } 418 | } 419 | -------------------------------------------------------------------------------- /src/sequence.rs: -------------------------------------------------------------------------------- 1 | use crate::Keyboard; 2 | use std::str::FromStr; 3 | use std::thread; 4 | use std::time::Duration; 5 | 6 | #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)] 7 | /// A sequence of events to execute. 8 | pub struct Sequence { 9 | sequence: Vec>, 10 | } 11 | 12 | impl Sequence { 13 | /// A Sequence of events to execute parsed from some text. 14 | /// Can only be created if all the keys are supported. upon encountering a key that cannot be 15 | /// represented will return None. 16 | pub fn text(text: &str) -> Option { 17 | let mut sequence = Vec::new(); 18 | for char in text.chars() { 19 | let uppercase = char.to_ascii_uppercase(); 20 | use Keyboard::*; 21 | let key = Keyboard::from_str(&uppercase.to_string()).ok()?; 22 | if char.is_uppercase() || char == ':' || char == '\"' { 23 | sequence.push(vec![LeftShift, key]) 24 | } else { 25 | sequence.push(vec![key]) 26 | } 27 | } 28 | Some(Sequence { sequence }) 29 | } 30 | 31 | /// send this Sequence on new thread. 32 | pub fn send(&self) { 33 | let cloned = self.clone(); 34 | thread::spawn(move || { 35 | for keys in &cloned.sequence { 36 | for key in keys { 37 | key.press(); 38 | } 39 | thread::sleep(Duration::from_millis(15)); 40 | for key in keys { 41 | key.release(); 42 | } 43 | thread::sleep(Duration::from_millis(15)); 44 | } 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/windows/keyboard.rs: -------------------------------------------------------------------------------- 1 | use crate::Keyboard; 2 | use std::convert::TryInto; 3 | use std::mem::size_of; 4 | use winapi::shared::minwindef::WORD; 5 | use winapi::um::winuser::{ 6 | INPUT_u, MapVirtualKeyW, SendInput, INPUT, INPUT_KEYBOARD, KEYBDINPUT, KEYEVENTF_KEYUP, 7 | KEYEVENTF_SCANCODE, LPINPUT, VK_ADD, VK_BACK, VK_CAPITAL, VK_DECIMAL, VK_DELETE, VK_DIVIDE, 8 | VK_DOWN, VK_ESCAPE, VK_F1, VK_F10, VK_F11, VK_F12, VK_F13, VK_F14, VK_F15, VK_F16, VK_F17, 9 | VK_F18, VK_F19, VK_F2, VK_F20, VK_F21, VK_F22, VK_F23, VK_F24, VK_F3, VK_F4, VK_F5, VK_F6, 10 | VK_F7, VK_F8, VK_F9, VK_HOME, VK_INSERT, VK_LCONTROL, VK_LEFT, VK_LMENU, VK_LSHIFT, VK_LWIN, 11 | VK_MULTIPLY, VK_NEXT, VK_NUMLOCK, VK_NUMPAD0, VK_NUMPAD1, VK_NUMPAD2, VK_NUMPAD3, VK_NUMPAD4, 12 | VK_NUMPAD5, VK_NUMPAD6, VK_NUMPAD7, VK_NUMPAD8, VK_NUMPAD9, VK_OEM_1, VK_OEM_2, VK_OEM_3, 13 | VK_OEM_4, VK_OEM_5, VK_OEM_6, VK_OEM_7, VK_OEM_COMMA, VK_OEM_PERIOD, VK_PRINT, VK_PRIOR, 14 | VK_RCONTROL, VK_RETURN, VK_RIGHT, VK_RMENU, VK_RSHIFT, VK_RWIN, VK_SCROLL, VK_SEPARATOR, 15 | VK_SNAPSHOT, VK_SPACE, VK_SUBTRACT, VK_TAB, VK_UP, VK_OEM_102 16 | }; 17 | 18 | pub(crate) mod kimpl { 19 | use crate::windows::keyboard::{send_key_stroke, vk_code}; 20 | use crate::Keyboard; 21 | use winapi::um::winuser::GetKeyState; 22 | 23 | pub(crate) fn press(key: Keyboard) { 24 | send_key_stroke(true, key) 25 | } 26 | 27 | pub(crate) fn release(key: Keyboard) { 28 | send_key_stroke(false, key) 29 | } 30 | 31 | pub(crate) fn click(key: Keyboard) { 32 | // Do we need sleep in between? 33 | press(key); 34 | release(key); 35 | } 36 | 37 | pub(crate) fn is_toggled(key: Keyboard) -> bool { 38 | // GetAsync is universal, but does not provide whether button is toggled. 39 | // as the GetKeyState seems to guarantee the correctness. 40 | let state = unsafe { GetKeyState(vk_code(key).into()) }; 41 | i32::from(state) & 0x8001 != 0 42 | } 43 | } 44 | 45 | pub fn send_key_stroke(press: bool, key: Keyboard) { 46 | let action = if press { 47 | 0 // 0 means to press. 48 | } else { 49 | KEYEVENTF_KEYUP 50 | }; 51 | unsafe { 52 | let mut input_u: INPUT_u = std::mem::zeroed(); 53 | *input_u.ki_mut() = KEYBDINPUT { 54 | wVk: 0, 55 | wScan: MapVirtualKeyW(vk_code(key).into(), 0) 56 | .try_into() 57 | .expect("Failed to map vk to scan code"), // This ignores the keyboard layout so better than vk? 58 | dwFlags: KEYEVENTF_SCANCODE | action, 59 | time: 0, 60 | dwExtraInfo: 0, 61 | }; 62 | 63 | let mut x = INPUT { 64 | type_: INPUT_KEYBOARD, 65 | u: input_u, 66 | }; 67 | 68 | SendInput(1, &mut x as LPINPUT, size_of::() as libc::c_int); 69 | } 70 | } 71 | 72 | // those dont have defines. 73 | const VK_0: i32 = 0x30; 74 | const VK_1: i32 = 0x31; 75 | const VK_2: i32 = 0x32; 76 | const VK_3: i32 = 0x33; 77 | const VK_4: i32 = 0x34; 78 | const VK_5: i32 = 0x35; 79 | const VK_6: i32 = 0x36; 80 | const VK_7: i32 = 0x37; 81 | const VK_8: i32 = 0x38; 82 | const VK_9: i32 = 0x39; 83 | const VK_A: i32 = 0x41; 84 | const VK_B: i32 = 0x42; 85 | const VK_C: i32 = 0x43; 86 | const VK_D: i32 = 0x44; 87 | const VK_E: i32 = 0x45; 88 | const VK_F: i32 = 0x46; 89 | const VK_G: i32 = 0x47; 90 | const VK_H: i32 = 0x48; 91 | const VK_I: i32 = 0x49; 92 | const VK_J: i32 = 0x4A; 93 | const VK_K: i32 = 0x4B; 94 | const VK_L: i32 = 0x4C; 95 | const VK_M: i32 = 0x4D; 96 | const VK_N: i32 = 0x4E; 97 | const VK_O: i32 = 0x4F; 98 | const VK_P: i32 = 0x50; 99 | const VK_Q: i32 = 0x51; 100 | const VK_R: i32 = 0x52; 101 | const VK_S: i32 = 0x53; 102 | const VK_T: i32 = 0x54; 103 | const VK_U: i32 = 0x55; 104 | const VK_V: i32 = 0x56; 105 | const VK_W: i32 = 0x57; 106 | const VK_X: i32 = 0x58; 107 | const VK_Y: i32 = 0x59; 108 | const VK_Z: i32 = 0x5A; 109 | 110 | fn vk_code(key: Keyboard) -> WORD { 111 | i32::from(key) 112 | .try_into() 113 | .expect("vk does not fit into WORD") 114 | } 115 | 116 | impl From for i32 { 117 | fn from(key: Keyboard) -> i32 { 118 | use Keyboard::*; 119 | match key { 120 | BackSpace => VK_BACK, 121 | Tab => VK_TAB, 122 | Enter => VK_RETURN, 123 | Escape => VK_ESCAPE, 124 | Space => VK_SPACE, 125 | PageUp => VK_PRIOR, 126 | PageDown => VK_NEXT, 127 | Home => VK_HOME, 128 | Left => VK_LEFT, 129 | Up => VK_UP, 130 | Right => VK_RIGHT, 131 | Down => VK_DOWN, 132 | Print => VK_PRINT, 133 | PrintScreen => VK_SNAPSHOT, 134 | Insert => VK_INSERT, 135 | Delete => VK_DELETE, 136 | Number0 => VK_0, 137 | Number1 => VK_1, 138 | Number2 => VK_2, 139 | Number3 => VK_3, 140 | Number4 => VK_4, 141 | Number5 => VK_5, 142 | Number6 => VK_6, 143 | Number7 => VK_7, 144 | Number8 => VK_8, 145 | Number9 => VK_9, 146 | A => VK_A, 147 | B => VK_B, 148 | C => VK_C, 149 | D => VK_D, 150 | E => VK_E, 151 | F => VK_F, 152 | G => VK_G, 153 | H => VK_H, 154 | I => VK_I, 155 | J => VK_J, 156 | K => VK_K, 157 | L => VK_L, 158 | M => VK_M, 159 | N => VK_N, 160 | O => VK_O, 161 | P => VK_P, 162 | Q => VK_Q, 163 | R => VK_R, 164 | S => VK_S, 165 | T => VK_T, 166 | U => VK_U, 167 | V => VK_V, 168 | W => VK_W, 169 | X => VK_X, 170 | Y => VK_Y, 171 | Z => VK_Z, 172 | LeftWindows => VK_LWIN, 173 | RightWindows => VK_RWIN, 174 | Numpad0 => VK_NUMPAD0, 175 | Numpad1 => VK_NUMPAD1, 176 | Numpad2 => VK_NUMPAD2, 177 | Numpad3 => VK_NUMPAD3, 178 | Numpad4 => VK_NUMPAD4, 179 | Numpad5 => VK_NUMPAD5, 180 | Numpad6 => VK_NUMPAD6, 181 | Numpad7 => VK_NUMPAD7, 182 | Numpad8 => VK_NUMPAD8, 183 | Numpad9 => VK_NUMPAD9, 184 | Multiply => VK_MULTIPLY, 185 | Add => VK_ADD, 186 | Separator => VK_SEPARATOR, 187 | Subtract => VK_SUBTRACT, 188 | Decimal => VK_DECIMAL, 189 | Divide => VK_DIVIDE, 190 | F1 => VK_F1, 191 | F2 => VK_F2, 192 | F3 => VK_F3, 193 | F4 => VK_F4, 194 | F5 => VK_F5, 195 | F6 => VK_F6, 196 | F7 => VK_F7, 197 | F8 => VK_F8, 198 | F9 => VK_F9, 199 | F10 => VK_F10, 200 | F11 => VK_F11, 201 | F12 => VK_F12, 202 | F13 => VK_F13, 203 | F14 => VK_F14, 204 | F15 => VK_F15, 205 | F16 => VK_F16, 206 | F17 => VK_F17, 207 | F18 => VK_F18, 208 | F19 => VK_F19, 209 | F20 => VK_F20, 210 | F21 => VK_F21, 211 | F22 => VK_F22, 212 | F23 => VK_F23, 213 | F24 => VK_F24, 214 | NumLock => VK_NUMLOCK, 215 | ScrollLock => VK_SCROLL, 216 | CapsLock => VK_CAPITAL, 217 | LeftShift => VK_LSHIFT, 218 | RightShift => VK_RSHIFT, 219 | LeftControl => VK_LCONTROL, 220 | RightControl => VK_RCONTROL, 221 | LeftAlt => VK_LMENU, 222 | RightAlt => VK_RMENU, 223 | Other(code) => code, 224 | Comma => VK_OEM_COMMA, 225 | Period => VK_OEM_PERIOD, 226 | Slash => VK_OEM_2, 227 | SemiColon => VK_OEM_1, 228 | Grave => VK_OEM_3, 229 | LeftBrace => VK_OEM_4, 230 | BackwardSlash => VK_OEM_5, 231 | RightBrace => VK_OEM_6, 232 | Apostrophe => VK_OEM_7, 233 | ThatThingy => VK_OEM_102 234 | } 235 | } 236 | } 237 | 238 | impl From for Keyboard { 239 | fn from(code: i32) -> Self { 240 | use Keyboard::*; 241 | match code { 242 | VK_BACK => BackSpace, 243 | VK_TAB => Tab, 244 | VK_RETURN => Enter, 245 | VK_ESCAPE => Escape, 246 | VK_SPACE => Space, 247 | VK_PRIOR => PageUp, 248 | VK_NEXT => PageDown, 249 | VK_HOME => Home, 250 | VK_LEFT => Left, 251 | VK_UP => Up, 252 | VK_RIGHT => Right, 253 | VK_DOWN => Down, 254 | VK_PRINT => Print, 255 | VK_SNAPSHOT => PrintScreen, 256 | VK_INSERT => Insert, 257 | VK_DELETE => Delete, 258 | VK_0 => Number0, 259 | VK_1 => Number1, 260 | VK_2 => Number2, 261 | VK_3 => Number3, 262 | VK_4 => Number4, 263 | VK_5 => Number5, 264 | VK_6 => Number6, 265 | VK_7 => Number7, 266 | VK_8 => Number8, 267 | VK_9 => Number9, 268 | VK_A => A, 269 | VK_B => B, 270 | VK_C => C, 271 | VK_D => D, 272 | VK_E => E, 273 | VK_F => F, 274 | VK_G => G, 275 | VK_H => H, 276 | VK_I => I, 277 | VK_J => J, 278 | VK_K => K, 279 | VK_L => L, 280 | VK_M => M, 281 | VK_N => N, 282 | VK_O => O, 283 | VK_P => P, 284 | VK_Q => Q, 285 | VK_R => R, 286 | VK_S => S, 287 | VK_T => T, 288 | VK_U => U, 289 | VK_V => V, 290 | VK_W => W, 291 | VK_X => X, 292 | VK_Y => Y, 293 | VK_Z => Z, 294 | VK_LWIN => LeftWindows, 295 | VK_RWIN => RightWindows, 296 | VK_NUMPAD0 => Numpad0, 297 | VK_NUMPAD1 => Numpad1, 298 | VK_NUMPAD2 => Numpad2, 299 | VK_NUMPAD3 => Numpad3, 300 | VK_NUMPAD4 => Numpad4, 301 | VK_NUMPAD5 => Numpad5, 302 | VK_NUMPAD6 => Numpad6, 303 | VK_NUMPAD7 => Numpad7, 304 | VK_NUMPAD8 => Numpad8, 305 | VK_NUMPAD9 => Numpad9, 306 | VK_MULTIPLY => Multiply, 307 | VK_ADD => Add, 308 | VK_SEPARATOR => Separator, 309 | VK_SUBTRACT => Subtract, 310 | VK_DECIMAL => Decimal, 311 | VK_DIVIDE => Divide, 312 | VK_F1 => F1, 313 | VK_F2 => F2, 314 | VK_F3 => F3, 315 | VK_F4 => F4, 316 | VK_F5 => F5, 317 | VK_F6 => F6, 318 | VK_F7 => F7, 319 | VK_F8 => F8, 320 | VK_F9 => F9, 321 | VK_F10 => F10, 322 | VK_F11 => F11, 323 | VK_F12 => F12, 324 | VK_F13 => F13, 325 | VK_F14 => F14, 326 | VK_F15 => F15, 327 | VK_F16 => F16, 328 | VK_F17 => F17, 329 | VK_F18 => F18, 330 | VK_F19 => F19, 331 | VK_F20 => F20, 332 | VK_F21 => F21, 333 | VK_F22 => F22, 334 | VK_F23 => F23, 335 | VK_F24 => F24, 336 | VK_NUMLOCK => NumLock, 337 | VK_SCROLL => ScrollLock, 338 | VK_CAPITAL => CapsLock, 339 | VK_LSHIFT => LeftShift, 340 | VK_RSHIFT => RightShift, 341 | VK_LCONTROL => LeftControl, 342 | VK_RCONTROL => RightControl, 343 | VK_LMENU => LeftAlt, 344 | VK_RMENU => RightAlt, 345 | VK_OEM_PERIOD => Period, 346 | VK_OEM_COMMA => Comma, 347 | VK_OEM_1 => SemiColon, 348 | VK_OEM_2 => Slash, 349 | VK_OEM_3 => Grave, 350 | VK_OEM_4 => LeftBrace, 351 | VK_OEM_5 => BackwardSlash, 352 | VK_OEM_6 => RightBrace, 353 | VK_OEM_7 => Apostrophe, 354 | _ => Other(code), 355 | } 356 | } 357 | } 358 | -------------------------------------------------------------------------------- /src/windows/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod keyboard; 2 | pub mod mouse; 3 | 4 | use crate::details::registry; 5 | use crate::{Event, InhibitEvent, Keyboard, Mouse}; 6 | use std::convert::TryInto; 7 | use std::mem::MaybeUninit; 8 | use std::ptr::null_mut; 9 | use winapi::shared::minwindef::{HINSTANCE, LPARAM, LRESULT, WPARAM}; 10 | use winapi::shared::windef::HHOOK__; 11 | use winapi::um::winuser::{ 12 | CallNextHookEx, GetMessageW, SetWindowsHookExW, GET_XBUTTON_WPARAM, KBDLLHOOKSTRUCT, MSG, 13 | WH_KEYBOARD_LL, WH_MOUSE_LL, WM_KEYDOWN, WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP, 14 | WM_MBUTTONDOWN, WM_MBUTTONUP, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SYSKEYDOWN, WM_SYSKEYUP, 15 | WM_XBUTTONDOWN, WM_XBUTTONUP, XBUTTON1, XBUTTON2, 16 | }; 17 | use winapi::um::winuser::{ 18 | MSLLHOOKSTRUCT, WM_LBUTTONDBLCLK, WM_MBUTTONDBLCLK, WM_RBUTTONDBLCLK, WM_XBUTTONDBLCLK, 19 | }; 20 | 21 | pub(crate) fn install_hooks() { 22 | install_hook(WH_KEYBOARD_LL, keybd_hook); 23 | install_hook(WH_MOUSE_LL, mouse_hook); 24 | } 25 | 26 | pub(crate) fn process_message() { 27 | let mut msg: MSG = unsafe { MaybeUninit::zeroed().assume_init() }; 28 | unsafe { GetMessageW(&mut msg, null_mut(), 0, 0) }; 29 | } 30 | 31 | fn install_hook( 32 | hook_id: libc::c_int, 33 | hook_proc: unsafe extern "system" fn(libc::c_int, WPARAM, LPARAM) -> LRESULT, 34 | ) -> *mut HHOOK__ { 35 | unsafe { SetWindowsHookExW(hook_id, Some(hook_proc), 0 as HINSTANCE, 0) } 36 | } 37 | 38 | unsafe extern "system" fn keybd_hook( 39 | code: libc::c_int, 40 | w_param: WPARAM, 41 | l_param: LPARAM, 42 | ) -> LRESULT { 43 | let vk: i32 = (*(l_param as *const KBDLLHOOKSTRUCT)) 44 | .vkCode 45 | .try_into() 46 | .expect("vkCode does not fit in i32"); 47 | // https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-keydown 48 | // Says that we can find the repeat bit here, however that does not apply to lowlvlkb hook which this is. 49 | // Because IDE is not capable of following to the definition here it is: 50 | // STRUCT!{struct KBDLLHOOKSTRUCT { 51 | // vkCode: DWORD, 52 | // scanCode: DWORD, 53 | // flags: DWORD, 54 | // time: DWORD, 55 | // dwExtraInfo: ULONG_PTR, 56 | // }} 57 | 58 | let mut inhibit = InhibitEvent::No; 59 | // Note this seemingly is only activated when ALT is not pressed, need to handle WM_SYSKEYDOWN then 60 | // Test that case. 61 | let key: Keyboard = vk.into(); 62 | match w_param as u32 { 63 | code if code == WM_KEYDOWN || code == WM_SYSKEYDOWN => { 64 | inhibit = registry().event_down(Event::Keyboard(key)); 65 | } 66 | code if code == WM_KEYUP || code == WM_SYSKEYUP => { 67 | inhibit = registry().event_up(Event::Keyboard(key)); 68 | } 69 | _ => {} 70 | } 71 | 72 | if inhibit.should_inhibit() { 73 | 1 74 | } else { 75 | CallNextHookEx(null_mut(), code, w_param, l_param) 76 | } 77 | } 78 | 79 | unsafe extern "system" fn mouse_hook( 80 | code: libc::c_int, 81 | w_param: WPARAM, 82 | l_param: LPARAM, 83 | ) -> LRESULT { 84 | // because macros > idea 85 | // typedef struct tagMSLLHOOKSTRUCT { 86 | // POINT pt; 87 | // DWORD mouseData; 88 | // DWORD flags; 89 | // DWORD time; 90 | // ULONG_PTR dwExtraInfo; 91 | // } MSLLHOOKSTRUCT, *LPMSLLHOOKSTRUCT, *PMSLLHOOKSTRUCT; 92 | 93 | let data = &*(l_param as *const MSLLHOOKSTRUCT); 94 | let x_button_param: u16 = 95 | GET_XBUTTON_WPARAM(data.mouseData.try_into().expect("u32 fits usize")); 96 | let maybe_x_button = if x_button_param == XBUTTON1 { 97 | Some(Mouse::Side) 98 | } else if x_button_param == XBUTTON2 { 99 | Some(Mouse::Extra) 100 | } else { 101 | None 102 | }; 103 | let w_param_u32: u32 = w_param.try_into().expect("w_param > u32"); 104 | registry().update_mouse_position(data.pt.x, data.pt.y); 105 | let inhibit = match w_param_u32 { 106 | code if code == WM_LBUTTONDOWN => registry().event_down(Event::Mouse(Mouse::Left)), 107 | code if code == WM_LBUTTONDBLCLK => registry().event_click(Event::Mouse(Mouse::DoubleLeft)), 108 | code if code == WM_RBUTTONDOWN => registry().event_down(Event::Mouse(Mouse::Right)), 109 | code if code == WM_RBUTTONDBLCLK => { 110 | registry().event_click(Event::Mouse(Mouse::DoubleRight)) 111 | } 112 | code if code == WM_MBUTTONDOWN => registry().event_down(Event::Mouse(Mouse::Middle)), 113 | code if code == WM_MBUTTONDBLCLK => { 114 | registry().event_down(Event::Mouse(Mouse::DoubleMiddle)) 115 | } 116 | code if code == WM_XBUTTONDOWN => { 117 | if let Some(x_button) = maybe_x_button { 118 | registry().event_down(Event::Mouse(x_button)) 119 | } else { 120 | InhibitEvent::No 121 | } 122 | } 123 | code if code == WM_XBUTTONDBLCLK => { 124 | if let Some(x_button) = maybe_x_button { 125 | // TODO: figure out the other XButtons. 126 | if Mouse::Side == x_button { 127 | registry().event_click(Event::Mouse(Mouse::DoubleSide)) 128 | } else { 129 | registry().event_click(Event::Mouse(Mouse::DoubleExtra)) 130 | } 131 | } else { 132 | InhibitEvent::No 133 | } 134 | } 135 | code if code == WM_LBUTTONUP => registry().event_up(Event::Mouse(Mouse::Left)), 136 | code if code == WM_LBUTTONUP => registry().event_up(Event::Mouse(Mouse::Left)), 137 | code if code == WM_RBUTTONUP => registry().event_up(Event::Mouse(Mouse::Right)), 138 | code if code == WM_RBUTTONUP => registry().event_up(Event::Mouse(Mouse::Right)), 139 | code if code == WM_MBUTTONUP => registry().event_up(Event::Mouse(Mouse::Middle)), 140 | code if code == WM_MBUTTONUP => registry().event_up(Event::Mouse(Mouse::Middle)), 141 | code if code == WM_XBUTTONUP => { 142 | if let Some(x_button) = maybe_x_button { 143 | registry().event_up(Event::Mouse(x_button)) 144 | } else { 145 | InhibitEvent::No 146 | } 147 | } 148 | code if code == WM_XBUTTONUP => { 149 | if let Some(x_button) = maybe_x_button { 150 | registry().event_up(Event::Mouse(x_button)) 151 | } else { 152 | InhibitEvent::No 153 | } 154 | } 155 | _ => InhibitEvent::No, 156 | }; 157 | if inhibit.should_inhibit() { 158 | 1 159 | } else { 160 | CallNextHookEx(null_mut(), code, w_param, l_param) 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/windows/mouse.rs: -------------------------------------------------------------------------------- 1 | use crate::Mouse; 2 | use std::mem; 3 | use std::mem::size_of; 4 | use winapi::um::winuser::{ 5 | INPUT_u, SendInput, INPUT, INPUT_MOUSE, LPINPUT, MOUSEEVENTF_ABSOLUTE, MOUSEEVENTF_LEFTDOWN, 6 | MOUSEEVENTF_LEFTUP, MOUSEEVENTF_MIDDLEDOWN, MOUSEEVENTF_MIDDLEUP, MOUSEEVENTF_MOVE, 7 | MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP, MOUSEEVENTF_XDOWN, MOUSEEVENTF_XUP, MOUSEINPUT, 8 | XBUTTON1, XBUTTON2, 9 | }; 10 | 11 | pub(crate) mod mimpl { 12 | use crate::windows::mouse::{ 13 | button_to_event_down, button_to_mouse_data, mouse_click, mouse_interact_with, mouse_press, 14 | mouse_release, Pos, 15 | }; 16 | use crate::Mouse; 17 | 18 | pub(crate) fn press(button: Mouse) { 19 | mouse_press(button) 20 | } 21 | 22 | pub(crate) fn click(button: Mouse) { 23 | mouse_click(button); 24 | } 25 | 26 | pub(crate) fn release(button: Mouse) { 27 | mouse_release(button); 28 | } 29 | 30 | // normalized absolute coordinates between 0 and 65,535 31 | // See remarks: https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-mouseinput#remarks 32 | // For now the library does not support a solution of it - caller is expected to solve it for himself 33 | // One of possible solutions would be to try to accept multiple parameters such as 34 | // "Screen Index + position on that screen index, but hey that sounds a bit too complex. sorry. 35 | pub(crate) fn move_to(x: i32, y: i32) { 36 | mouse_interact_with(0, 0, Some(Pos::absolute(x, y))); 37 | } 38 | 39 | // Unlike move_to this uses a human friendly coordinates, 10 is 10 pixels. 40 | pub(crate) fn move_by(x: i32, y: i32) { 41 | mouse_interact_with(0, 0, Some(Pos::relative(x, y))); 42 | } 43 | 44 | pub(crate) fn click_at(x: i32, y: i32, button: Mouse) { 45 | mouse_interact_with( 46 | button_to_event_down(button), 47 | button_to_mouse_data(button), 48 | Some(Pos::absolute(x, y)), 49 | ) 50 | } 51 | } 52 | 53 | struct Pos { 54 | x: i32, 55 | y: i32, 56 | absolute: bool, 57 | } 58 | 59 | impl Pos { 60 | fn absolute(x: i32, y: i32) -> Self { 61 | Pos { 62 | x, 63 | y, 64 | absolute: true, 65 | } 66 | } 67 | 68 | fn relative(x: i32, y: i32) -> Self { 69 | Pos { 70 | x, 71 | y, 72 | absolute: false, 73 | } 74 | } 75 | } 76 | 77 | fn mouse_interact_with(mut interaction: u32, mouse_data: u16, pos: Option) { 78 | let mut x = 0; 79 | let mut y = 0; 80 | if let Some(pos) = pos { 81 | if pos.absolute { 82 | interaction |= MOUSEEVENTF_ABSOLUTE; 83 | } 84 | x = pos.x; 85 | y = pos.y; 86 | interaction |= MOUSEEVENTF_MOVE; 87 | } 88 | unsafe { 89 | let mut input: INPUT_u = mem::zeroed(); 90 | *input.mi_mut() = MOUSEINPUT { 91 | dx: x, 92 | dy: y, 93 | mouseData: mouse_data.into(), 94 | time: 0, 95 | dwFlags: interaction, 96 | dwExtraInfo: 0, 97 | }; 98 | let mut x = INPUT { 99 | type_: INPUT_MOUSE, 100 | u: input, 101 | }; 102 | 103 | SendInput(1, &mut x as LPINPUT, size_of::() as libc::c_int); 104 | } 105 | } 106 | 107 | pub fn mouse_press(button: Mouse) { 108 | let click = button_to_event_down(button) | button_to_event_up(button); 109 | mouse_interact_with(click, button_to_mouse_data(button), mouse_to_pos(button)) 110 | } 111 | 112 | pub fn mouse_release(button: Mouse) { 113 | mouse_interact_with( 114 | button_to_event_up(button), 115 | button_to_mouse_data(button), 116 | mouse_to_pos(button), 117 | ) 118 | } 119 | 120 | pub fn mouse_click(button: Mouse) { 121 | mouse_interact_with( 122 | button_to_event_down(button), 123 | button_to_mouse_data(button), 124 | mouse_to_pos(button), 125 | ) 126 | } 127 | 128 | fn button_to_mouse_data(button: Mouse) -> u16 { 129 | match button { 130 | Mouse::Side | Mouse::DoubleSide => XBUTTON1, 131 | Mouse::Extra | Mouse::DoubleExtra => XBUTTON2, 132 | _ => 0, 133 | } 134 | } 135 | 136 | fn button_to_event_up(button: Mouse) -> u32 { 137 | use Mouse::*; 138 | match button { 139 | Left | DoubleLeft => MOUSEEVENTF_LEFTDOWN, 140 | Right | DoubleRight => MOUSEEVENTF_RIGHTDOWN, 141 | Middle | DoubleMiddle => MOUSEEVENTF_MIDDLEDOWN, 142 | Side | DoubleSide | Extra | DoubleExtra => MOUSEEVENTF_XDOWN, 143 | } 144 | } 145 | 146 | fn button_to_event_down(button: Mouse) -> u32 { 147 | use Mouse::*; 148 | match button { 149 | Left | DoubleLeft => MOUSEEVENTF_LEFTUP, 150 | Right | DoubleRight => MOUSEEVENTF_RIGHTUP, 151 | Middle | DoubleMiddle => MOUSEEVENTF_MIDDLEUP, 152 | Side | DoubleSide | Extra | DoubleExtra => MOUSEEVENTF_XUP, 153 | } 154 | } 155 | 156 | fn mouse_to_pos(button: Mouse) -> Option { 157 | use Mouse::*; 158 | match button { 159 | Left | DoubleLeft => None, 160 | Right | DoubleRight => None, 161 | Middle | DoubleMiddle => None, 162 | Side | DoubleSide | Extra | DoubleExtra => None, 163 | } 164 | } 165 | --------------------------------------------------------------------------------