├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── hookmap-core ├── Cargo.toml ├── README.md ├── examples │ └── readme_core.rs └── src │ ├── button.rs │ ├── event.rs │ ├── lib.rs │ ├── sys.rs │ └── sys │ ├── windows.rs │ └── windows │ ├── hook.rs │ ├── input.rs │ └── vkcode.rs └── hookmap ├── Cargo.toml ├── README.md ├── examples ├── alt_tab.rs ├── readme.rs └── sands.rs └── src ├── hook.rs ├── hotkey.rs ├── hotkey ├── context.rs ├── hook.rs └── storage.rs ├── lib.rs ├── macros.rs ├── macros ├── button_arg.rs └── sequence.rs ├── runtime.rs ├── runtime ├── button_state.rs ├── event_broker.rs └── interceptor.rs └── utils.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | /.cargo 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "hookmap-core", 5 | "hookmap" 6 | ] 7 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 keke1008 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hookmap 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/hookmap.svg)](https://crates.io/crates/hookmap) 4 | [![API reference](https://docs.rs/hookmap/badge.svg)](https://docs.rs/hookmap) 5 | 6 | A rust library for Register hotkeys and emulate keyboard and mouse input. 7 | 8 | ## Supported OS 9 | 10 | * Windows 10 11 | 12 | ## Example 13 | 14 | ```rust 15 | use hookmap::prelude::*; 16 | 17 | fn main() { 18 | let mut hotkey = Hotkey::new(); 19 | 20 | // Remap H,J,K,L keys as in vim. 21 | hotkey 22 | .register(Context::default()) 23 | .remap(Button::H, Button::LeftArrow) 24 | .remap(Button::J, Button::DownArrow) 25 | .remap(Button::K, Button::UpArrow) 26 | .remap(Button::L, Button::RightArrow); 27 | 28 | // You can define hotkeys that work only when specific keys are pressed or released. 29 | hotkey 30 | .register( 31 | Context::new() 32 | .modifiers(buttons!(LCtrl, !RShift)) 33 | .native_event_operation(NativeEventOperation::Block), 34 | ) 35 | .on_press(Button::Space, |_| { 36 | seq!(with(LCtrl), A).send_ignore_modifiers(); 37 | }) 38 | .disable(buttons!(A, B)) 39 | .on_release(buttons!(A, B), |event: ButtonEvent| { 40 | seq!(with(LShift), [event.target]).send_ignore_modifiers(); 41 | }); 42 | 43 | hotkey.install(); 44 | } 45 | ``` 46 | -------------------------------------------------------------------------------- /hookmap-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hookmap-core" 3 | version = "0.2.1" 4 | authors = ["keke1008 "] 5 | edition = "2021" 6 | description = "Global hooks and input simulation fo keyboard and mouse." 7 | license = "MIT OR Apache-2.0" 8 | repository = "https://github.com/keke1008/hookmap" 9 | keywords = ["hotkey", "keyboard", "mouse"] 10 | categories = ["api-bindings"] 11 | readme = "README.md" 12 | documentation = "https://docs.rs/hookmap-core" 13 | 14 | [dependencies] 15 | once_cell = "1.8.0" 16 | variant_count = "1.1.0" 17 | 18 | [target.'cfg(windows)'.dependencies] 19 | windows = { version = "0.36.1", features = [ 20 | "Win32_Foundation", 21 | "Win32_System_Threading", 22 | "Win32_UI_WindowsAndMessaging", 23 | "Win32_UI_HiDpi", 24 | "Win32_UI_Input_KeyboardAndMouse" 25 | ]} 26 | 27 | [features] 28 | us-keyboard-layout = [] 29 | japanese-keyboard-layout = [] 30 | 31 | [package.metadata.docs.rs] 32 | targets = ["x86_64-pc-windows-msvc"] 33 | -------------------------------------------------------------------------------- /hookmap-core/README.md: -------------------------------------------------------------------------------- 1 | # hookmap-core 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/hookmap-core.svg)](https://crates.io/crates/hookmap-core) 4 | [![API reference](https://docs.rs/hookmap-core/badge.svg)](https://docs.rs/hookmap-core) 5 | 6 | A core library of [hookmap](https://crates.io/crates/hookmap) 7 | 8 | This library provides input simulation and global hooks for keyboard and mouse. 9 | 10 | ## Supported OS 11 | 12 | * Windows 10 13 | 14 | ## Eample 15 | 16 | ```rust 17 | use hookmap_core::{button::Button, event::Event, mouse}; 18 | 19 | fn main() { 20 | let rx = hookmap_core::install_hook(); 21 | 22 | while let Ok((event, native_handler)) = rx.recv() { 23 | match event { 24 | Event::Button(event) => { 25 | native_handler.dispatch(); 26 | 27 | match event.target { 28 | Button::RightArrow => println!("Left"), 29 | Button::UpArrow => println!("Up"), 30 | Button::LeftArrow => println!("Right"), 31 | Button::DownArrow => println!("Down"), 32 | _ => {} 33 | }; 34 | } 35 | 36 | Event::Cursor(e) => { 37 | native_handler.block(); 38 | 39 | // Reverses mouse cursor movement 40 | let (dx, dy) = e.delta; 41 | mouse::move_relative(-dx, -dy); 42 | } 43 | 44 | Event::Wheel(e) => { 45 | native_handler.dispatch(); 46 | println!("delta: {}", e.delta); 47 | } 48 | } 49 | } 50 | } 51 | ``` 52 | -------------------------------------------------------------------------------- /hookmap-core/examples/readme_core.rs: -------------------------------------------------------------------------------- 1 | use hookmap_core::{button::Button, event::Event, mouse}; 2 | 3 | fn main() { 4 | let rx = hookmap_core::install_hook(); 5 | 6 | while let Ok((event, native_handler)) = rx.recv() { 7 | match event { 8 | Event::Button(event) => { 9 | native_handler.dispatch(); 10 | 11 | match event.target { 12 | Button::RightArrow => println!("Left"), 13 | Button::UpArrow => println!("Up"), 14 | Button::LeftArrow => println!("Right"), 15 | Button::DownArrow => println!("Down"), 16 | _ => {} 17 | }; 18 | } 19 | 20 | Event::Cursor(e) => { 21 | native_handler.block(); 22 | 23 | // Reverses mouse cursor movement 24 | let (dx, dy) = e.delta; 25 | mouse::move_relative(-dx, -dy); 26 | } 27 | 28 | Event::Wheel(e) => { 29 | native_handler.dispatch(); 30 | println!("delta: {}", e.delta); 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /hookmap-core/src/button.rs: -------------------------------------------------------------------------------- 1 | //! Definition of keyboard and mouse button. 2 | 3 | use variant_count::VariantCount; 4 | 5 | /// A button input action. 6 | #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] 7 | pub enum ButtonAction { 8 | Press, 9 | Release, 10 | } 11 | 12 | /// Indicates whether the button is on the keyboard or mouse. 13 | #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] 14 | pub enum ButtonKind { 15 | /// On the keyboard 16 | Key, 17 | 18 | /// On the mouse 19 | Mouse, 20 | } 21 | 22 | /// Keyboard or mouse buttons. 23 | #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, VariantCount)] 24 | pub enum Button { 25 | LeftButton, 26 | RightButton, 27 | MiddleButton, 28 | SideButton1, 29 | SideButton2, 30 | 31 | #[cfg(feature = "us-keyboard-layout")] 32 | Tilde, 33 | #[cfg(feature = "japanese-keyboard-layout")] 34 | HankakuZenkaku, 35 | 36 | Key1, 37 | Key2, 38 | Key3, 39 | Key4, 40 | Key5, 41 | Key6, 42 | Key7, 43 | Key8, 44 | Key9, 45 | Key0, 46 | Minus, 47 | 48 | #[cfg(feature = "us-keyboard-layout")] 49 | Equal, 50 | #[cfg(feature = "japanese-keyboard-layout")] 51 | Hat, 52 | 53 | #[cfg(feature = "japanese-keyboard-layout")] 54 | Yen, 55 | 56 | Backspace, 57 | Tab, 58 | Q, 59 | W, 60 | E, 61 | R, 62 | T, 63 | Y, 64 | U, 65 | I, 66 | O, 67 | P, 68 | 69 | #[cfg(feature = "us-keyboard-layout")] 70 | OpenSquareBracket, 71 | #[cfg(feature = "japanese-keyboard-layout")] 72 | At, 73 | 74 | #[cfg(feature = "us-keyboard-layout")] 75 | CloseSquareBracket, 76 | #[cfg(feature = "japanese-keyboard-layout")] 77 | OpenSquareBracket, 78 | 79 | #[cfg(feature = "us-keyboard-layout")] 80 | CapsLock, 81 | #[cfg(feature = "japanese-keyboard-layout")] 82 | Eisu, 83 | 84 | A, 85 | S, 86 | D, 87 | F, 88 | G, 89 | H, 90 | J, 91 | K, 92 | L, 93 | 94 | #[cfg(feature = "us-keyboard-layout")] 95 | SemiColon, 96 | #[cfg(feature = "japanese-keyboard-layout")] 97 | SemiColon, 98 | 99 | #[cfg(feature = "us-keyboard-layout")] 100 | SingleQuote, 101 | #[cfg(feature = "japanese-keyboard-layout")] 102 | Colon, 103 | 104 | #[cfg(feature = "japanese-keyboard-layout")] 105 | CloseSquareBracket, 106 | 107 | Enter, 108 | LShift, 109 | Z, 110 | X, 111 | C, 112 | V, 113 | B, 114 | N, 115 | M, 116 | Comma, 117 | Dot, 118 | Slash, 119 | 120 | #[cfg(feature = "japanese-keyboard-layout")] 121 | BackSlash, 122 | 123 | RShift, 124 | LCtrl, 125 | LSuper, 126 | LAlt, 127 | 128 | #[cfg(feature = "japanese-keyboard-layout")] 129 | Muhenkan, 130 | 131 | Space, 132 | 133 | #[cfg(feature = "japanese-keyboard-layout")] 134 | Henkan, 135 | 136 | #[cfg(feature = "japanese-keyboard-layout")] 137 | KatakanaHiragana, 138 | 139 | RAlt, 140 | RSuper, 141 | Application, 142 | RCtrl, 143 | Insert, 144 | Delete, 145 | LeftArrow, 146 | Home, 147 | End, 148 | UpArrow, 149 | DownArrow, 150 | PageUp, 151 | PageDown, 152 | RightArrow, 153 | Numpad1, 154 | Numpad2, 155 | Numpad3, 156 | Numpad4, 157 | Numpad5, 158 | Numpad6, 159 | Numpad7, 160 | Numpad8, 161 | Numpad9, 162 | Numpad0, 163 | NumpadDot, 164 | NumpadSlash, 165 | NumpadAsterisk, 166 | NumpadMinus, 167 | NumpadPlus, 168 | Esc, 169 | F1, 170 | F2, 171 | F3, 172 | F4, 173 | F5, 174 | F6, 175 | F7, 176 | F8, 177 | F9, 178 | F10, 179 | F11, 180 | F12, 181 | F13, 182 | F14, 183 | F15, 184 | F16, 185 | F17, 186 | F18, 187 | F19, 188 | F20, 189 | F21, 190 | F22, 191 | F23, 192 | F24, 193 | PrintScreen, 194 | 195 | VolumeMute, 196 | VolumeDown, 197 | VolumeUp, 198 | MediaNext, 199 | MediaPrevious, 200 | MediaStop, 201 | MediaPlayPause, 202 | 203 | Shift, 204 | Ctrl, 205 | Alt, 206 | Super, 207 | } 208 | 209 | impl Button { 210 | pub fn kind(&self) -> ButtonKind { 211 | match self { 212 | Button::LeftButton 213 | | Button::RightButton 214 | | Button::MiddleButton 215 | | Button::SideButton1 216 | | Button::SideButton2 => ButtonKind::Mouse, 217 | _ => ButtonKind::Key, 218 | } 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /hookmap-core/src/event.rs: -------------------------------------------------------------------------------- 1 | //! Representation of keyboard and mouse events. 2 | //! 3 | //! When an event is generated, [`EventReceiver::recv`] can be called to receive the event. 4 | //! The received event is blocked (other programs are not yet notified). 5 | //! 6 | //! Calling [`NativeEventHandler::block`] will continue blocking and no notification will be made. 7 | //! Alternatively, calling [`NativeEventHandler::dispatch`] will notify other programs of the event. 8 | //! If neither is called, the event is notified. 9 | //! 10 | //! # Warning 11 | //! 12 | //! On Windows, Calling function that perform input (e.g. [`Button::press`]) before calling [`NativeEventHandler::block`] 13 | //! or [`NativeEventHandler::dispatch`] will take time. 14 | //! Therefore, call these functions before calling function that performs the input. 15 | //! 16 | //! # Examples 17 | //! 18 | //! ```no_run 19 | //! let rx = hookmap_core::install_hook(); 20 | //! while let Ok((event, native_handler)) = rx.recv() { 21 | //! // Blocks all the events 22 | //! native_handler.block(); 23 | //! } 24 | //! ``` 25 | 26 | use super::button::{Button, ButtonAction}; 27 | use std::sync::mpsc::{self, Receiver, Sender, SyncSender}; 28 | 29 | /// Indicates whether to pass the generated event to the next program or not. 30 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 31 | pub enum NativeEventOperation { 32 | /// Do not pass the generated event to the next program. 33 | Block, 34 | 35 | /// Pass the generated event to the next program. 36 | Dispatch, 37 | } 38 | 39 | impl Default for &NativeEventOperation { 40 | fn default() -> Self { 41 | &NativeEventOperation::Dispatch 42 | } 43 | } 44 | 45 | impl Default for NativeEventOperation { 46 | fn default() -> Self { 47 | *<&NativeEventOperation>::default() 48 | } 49 | } 50 | 51 | /// Indicates button event. 52 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 53 | pub struct ButtonEvent { 54 | /// Target of the generated event. 55 | pub target: Button, 56 | 57 | /// Action of the generated event. 58 | pub action: ButtonAction, 59 | 60 | /// Whether this event was generated by this program. 61 | /// If you type on your keyboard and an event is generated, this value will be `false`. 62 | pub injected: bool, 63 | } 64 | 65 | /// Indicates mouse cursor event. 66 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 67 | pub struct CursorEvent { 68 | /// Mouse cursor movement `(x, y)` 69 | pub delta: (i32, i32), 70 | 71 | /// Whether this event was generated by this program. 72 | pub injected: bool, 73 | } 74 | 75 | /// Indicates mouse wheel event. 76 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 77 | pub struct WheelEvent { 78 | /// Amout of mouse wheel rotation 79 | /// Upward rotation takes a positive value, downward rotation a negative value. 80 | pub delta: i32, 81 | 82 | /// Whether this event was generated by this program. 83 | pub injected: bool, 84 | } 85 | 86 | /// An event 87 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 88 | pub enum Event { 89 | /// Button event 90 | Button(ButtonEvent), 91 | 92 | /// Mouse wheel event 93 | Wheel(WheelEvent), 94 | 95 | /// Mouse cursor event 96 | Cursor(CursorEvent), 97 | } 98 | 99 | /// Decide whether to notify other programs of generated events. 100 | #[derive(Debug)] 101 | pub struct NativeEventHandler { 102 | tx: Option>, 103 | } 104 | 105 | impl NativeEventHandler { 106 | fn new(tx: Sender) -> Self { 107 | Self { tx: Some(tx) } 108 | } 109 | 110 | /// Decides whether or not to notify by argument. 111 | pub fn handle(mut self, operation: NativeEventOperation) { 112 | self.tx.take().unwrap().send(operation).unwrap(); 113 | } 114 | 115 | // Notifies an event. 116 | pub fn dispatch(self) { 117 | self.handle(NativeEventOperation::Dispatch); 118 | } 119 | 120 | // Does not notify an event. 121 | pub fn block(self) { 122 | self.handle(NativeEventOperation::Block); 123 | } 124 | } 125 | 126 | impl Drop for NativeEventHandler { 127 | fn drop(&mut self) { 128 | if let Some(tx) = self.tx.take() { 129 | tx.send(NativeEventOperation::default()).unwrap(); 130 | } 131 | } 132 | } 133 | 134 | #[derive(Debug, Clone)] 135 | pub(crate) struct EventSender { 136 | tx: SyncSender<(Event, NativeEventHandler)>, 137 | } 138 | 139 | impl EventSender { 140 | pub(crate) fn new(tx: SyncSender<(Event, NativeEventHandler)>) -> Self { 141 | Self { tx } 142 | } 143 | 144 | pub(crate) fn send(&self, event: Event) -> NativeEventOperation { 145 | let (tx, rx) = mpsc::channel(); 146 | let sent_data = (event, NativeEventHandler::new(tx)); 147 | 148 | self.tx.send(sent_data).unwrap(); 149 | rx.recv().unwrap() 150 | } 151 | } 152 | 153 | pub type EventReceiver = Receiver<(Event, NativeEventHandler)>; 154 | 155 | pub(crate) fn channel() -> (EventSender, EventReceiver) { 156 | const BOUND: usize = 1; 157 | let (tx, rx) = mpsc::sync_channel(BOUND); 158 | (EventSender::new(tx), rx) 159 | } 160 | -------------------------------------------------------------------------------- /hookmap-core/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A core library of [hookmap]. 2 | //! 3 | //! [hookmap]: https://crates.io/crates/hookmap 4 | //! 5 | //! This library provides input simulation and global hooks for keyboard and mouse. 6 | //! 7 | //! ## Feature flags 8 | //! 9 | //! * `us-keyboard-layout` (default): Use US keyboard layout. This changes the [`Button`] variant. 10 | //! * `japanese-keyboard-layout`: Use Japanese keyboard layout. This changes the [`Button`] variant. 11 | //! 12 | //! [`Button`]: button::Button 13 | //! 14 | 15 | pub mod button; 16 | pub mod event; 17 | 18 | mod sys; 19 | 20 | pub use sys::{install_hook, mouse, uninstall_hook}; 21 | -------------------------------------------------------------------------------- /hookmap-core/src/sys.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_os = "windows")] 2 | mod windows; 3 | 4 | #[cfg(target_os = "windows")] 5 | pub use self::windows::{install_hook, mouse, uninstall_hook}; 6 | -------------------------------------------------------------------------------- /hookmap-core/src/sys/windows.rs: -------------------------------------------------------------------------------- 1 | mod hook; 2 | mod input; 3 | mod vkcode; 4 | 5 | use hook::HookHandler; 6 | use input::Input; 7 | use windows::Win32::Foundation::{LPARAM, LRESULT, WPARAM}; 8 | use windows::Win32::UI::WindowsAndMessaging::HHOOK; 9 | 10 | use crate::button::{Button, ButtonAction}; 11 | use crate::event::{self, EventReceiver, NativeEventOperation}; 12 | 13 | use std::sync::atomic::{AtomicBool, Ordering}; 14 | 15 | use once_cell::sync::Lazy; 16 | use windows::Win32::UI::{HiDpi, WindowsAndMessaging}; 17 | 18 | const SHOULD_BE_IGNORED_FLAG: usize = 0x1; 19 | const INJECTED_FLAG: usize = 0x2; 20 | 21 | #[derive(Debug)] 22 | struct ButtonState([AtomicBool; Button::VARIANT_COUNT]); 23 | 24 | impl ButtonState { 25 | const fn new() -> Self { 26 | let inner = unsafe { 27 | // AtomicBool has the same in-memory representation as a bool. 28 | // https://doc.rust-lang.org/std/sync/atomic/struct.AtomicBool.html 29 | std::mem::transmute([false; Button::VARIANT_COUNT]) 30 | }; 31 | ButtonState(inner) 32 | } 33 | 34 | #[inline] 35 | fn press(&self, button: Button, order: Ordering) { 36 | self.0[button as usize].store(true, order); 37 | } 38 | 39 | #[inline] 40 | fn release(&self, button: Button, order: Ordering) { 41 | self.0[button as usize].store(false, order) 42 | } 43 | 44 | #[inline] 45 | fn is_pressed(&self, button: Button, order: Ordering) -> bool { 46 | self.0[button as usize].load(order) 47 | } 48 | 49 | #[inline] 50 | fn is_released(&self, button: Button, order: Ordering) -> bool { 51 | !self.0[button as usize].load(order) 52 | } 53 | } 54 | 55 | static BUTTON_STATE: ButtonState = ButtonState::new(); 56 | 57 | static INPUT: Lazy = Lazy::new(Input::new); 58 | 59 | #[inline] 60 | fn send_input(button: Button, action: ButtonAction, recursive: bool, assume: fn(Button)) { 61 | let left_and_right_modifier = match button { 62 | Button::Shift => Some((Button::LShift, Button::RShift)), 63 | Button::Ctrl => Some((Button::LCtrl, Button::RCtrl)), 64 | Button::Alt => Some((Button::LAlt, Button::RAlt)), 65 | Button::Super => Some((Button::LSuper, Button::RSuper)), 66 | _ => None, 67 | }; 68 | if let Some((left, right)) = left_and_right_modifier { 69 | assume(left); 70 | assume(right); 71 | assume(button); 72 | INPUT.button_input(left, action, recursive); 73 | INPUT.button_input(right, action, recursive); 74 | } else { 75 | assume(button); 76 | INPUT.button_input(button, action, recursive); 77 | } 78 | } 79 | 80 | impl Button { 81 | /// Simulates a button presses. 82 | #[inline] 83 | pub fn press(self) { 84 | send_input(self, ButtonAction::Press, false, Button::assume_pressed); 85 | } 86 | 87 | /// Simulates a button presses. 88 | /// Events generated by this method can be hooked. 89 | #[inline] 90 | pub fn press_recursive(self) { 91 | send_input(self, ButtonAction::Press, true, Button::assume_pressed); 92 | } 93 | 94 | /// Simulates a button releases. 95 | #[inline] 96 | pub fn release(self) { 97 | send_input(self, ButtonAction::Release, false, Button::assume_released); 98 | } 99 | 100 | /// Simulates a button releases. 101 | /// Events generated by this method can be hooked. 102 | #[inline] 103 | pub fn release_recursive(self) { 104 | send_input(self, ButtonAction::Release, true, Button::assume_released); 105 | } 106 | 107 | /// Simulates a button click. 108 | #[inline] 109 | pub fn click(self) { 110 | self.press(); 111 | self.release(); 112 | } 113 | 114 | /// Simulates a button click. 115 | /// Events generated by this method can be hooked. 116 | #[inline] 117 | pub fn click_recursive(self) { 118 | self.press_recursive(); 119 | self.release_recursive(); 120 | } 121 | 122 | /// Returns `true` if the button is pressed. 123 | #[inline] 124 | pub fn is_pressed(self) -> bool { 125 | BUTTON_STATE.is_pressed(self, Ordering::SeqCst) 126 | } 127 | 128 | /// Returns `true` if the button is released. 129 | #[inline] 130 | pub fn is_released(self) -> bool { 131 | BUTTON_STATE.is_released(self, Ordering::SeqCst) 132 | } 133 | 134 | #[inline] 135 | fn assume_pressed(self) { 136 | BUTTON_STATE.press(self, Ordering::SeqCst); 137 | } 138 | 139 | #[inline] 140 | fn assume_released(self) { 141 | BUTTON_STATE.release(self, Ordering::SeqCst); 142 | } 143 | } 144 | 145 | pub mod mouse { 146 | //! Functions for mouse operations 147 | 148 | use super::INPUT; 149 | 150 | /// Gets the position of the mouse cursor. `(x, y)` 151 | #[inline] 152 | pub fn get_position() -> (i32, i32) { 153 | INPUT.cursor_position() 154 | } 155 | 156 | /// Moves the mouse cursor to the specified coordinates. 157 | #[inline] 158 | pub fn move_absolute(x: i32, y: i32) { 159 | INPUT.move_absolute(x, y, false); 160 | } 161 | 162 | /// Moves the mouse cursor to the specified coordinates. 163 | /// Events generated by this method can be hooked. 164 | #[inline] 165 | pub fn move_absolute_recursive(x: i32, y: i32) { 166 | INPUT.move_absolute(x, y, true); 167 | } 168 | 169 | /// Moves the mouse cursor a specified distance. 170 | #[inline] 171 | pub fn move_relative(dx: i32, dy: i32) { 172 | INPUT.move_relative(dx, dy, false); 173 | } 174 | 175 | /// Moves the mouse cursor a specified distance. 176 | /// Events generated by this method can be hooked. 177 | #[inline] 178 | pub fn move_relative_recursive(dx: i32, dy: i32) { 179 | INPUT.move_relative(dx, dy, true); 180 | } 181 | 182 | /// Rotates the mouse wheel. 183 | #[inline] 184 | pub fn rotate(speed: i32) { 185 | INPUT.rotate_wheel(speed, false); 186 | } 187 | 188 | /// Rotates the mouse wheel. 189 | /// Events generated by this method can be hooked. 190 | #[inline] 191 | pub fn rotate_recursive(speed: i32) { 192 | INPUT.rotate_wheel(speed, true); 193 | } 194 | } 195 | 196 | static HOOK_HANDLER: Lazy = Lazy::new(HookHandler::new); 197 | 198 | extern "system" fn keyboard_hook_proc(n_code: i32, w_param: WPARAM, l_param: LPARAM) -> LRESULT { 199 | match hook::keyboard_hook_proc_inner(&HOOK_HANDLER, n_code, l_param) { 200 | NativeEventOperation::Block => LRESULT(1), 201 | NativeEventOperation::Dispatch => unsafe { 202 | WindowsAndMessaging::CallNextHookEx(HHOOK(0), n_code, w_param, l_param) 203 | }, 204 | } 205 | } 206 | 207 | extern "system" fn mouse_hook_proc(n_code: i32, w_param: WPARAM, l_param: LPARAM) -> LRESULT { 208 | match hook::mouse_hook_proc_inner(&HOOK_HANDLER, &INPUT, n_code, w_param, l_param) { 209 | NativeEventOperation::Block => LRESULT(1), 210 | NativeEventOperation::Dispatch => unsafe { 211 | WindowsAndMessaging::CallNextHookEx(HHOOK(0), n_code, w_param, l_param) 212 | }, 213 | } 214 | } 215 | 216 | /// Installs a hook and returns a receiver to receive the generated event. 217 | /// 218 | /// # Panics 219 | /// 220 | /// Panics if other hooks are already installed. 221 | /// 222 | /// # Example 223 | /// 224 | /// ```no_run 225 | /// let rx = hookmap_core::install_hook(); 226 | /// ``` 227 | /// 228 | pub fn install_hook() -> EventReceiver { 229 | unsafe { 230 | // If this is not executed, the GetCursorPos function returns an invalid cursor position. 231 | HiDpi::SetProcessDpiAwarenessContext(HiDpi::DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE); 232 | } 233 | 234 | INPUT.update_cursor_position(); 235 | 236 | let (tx, rx) = event::channel(); 237 | HOOK_HANDLER.install(tx, keyboard_hook_proc, mouse_hook_proc); 238 | 239 | rx 240 | } 241 | 242 | /// Uninstalls a hook. 243 | /// After this call, [`install_hook`] can be called again. 244 | /// 245 | /// # Panics 246 | /// 247 | /// Panics if the hook is not installed. 248 | /// 249 | /// # Example 250 | /// 251 | /// ```no_run 252 | /// let rx = hookmap_core::install_hook(); 253 | /// hookmap_core::uninstall_hook(); 254 | /// 255 | /// assert!(rx.recv().is_err()); 256 | /// 257 | /// let rx = hookmap_core::install_hook(); 258 | /// ``` 259 | /// 260 | pub fn uninstall_hook() { 261 | HOOK_HANDLER.uninstall(); 262 | } 263 | -------------------------------------------------------------------------------- /hookmap-core/src/sys/windows/hook.rs: -------------------------------------------------------------------------------- 1 | use super::input::Input; 2 | use super::{vkcode, INJECTED_FLAG, SHOULD_BE_IGNORED_FLAG}; 3 | use crate::button::{Button, ButtonAction}; 4 | use crate::event::{ 5 | ButtonEvent, CursorEvent, Event, EventSender, NativeEventOperation, WheelEvent, 6 | }; 7 | 8 | use std::mem::MaybeUninit; 9 | use std::sync::mpsc::Sender; 10 | use std::sync::{mpsc, Mutex}; 11 | use std::thread::{self, JoinHandle}; 12 | 13 | use windows::Win32::Foundation::{HINSTANCE, HWND, LPARAM, LRESULT, WPARAM}; 14 | use windows::Win32::System::Threading; 15 | use windows::Win32::UI::Input::KeyboardAndMouse::VIRTUAL_KEY; 16 | use windows::Win32::UI::WindowsAndMessaging; 17 | 18 | // For many constants. 19 | use windows::Win32::UI::WindowsAndMessaging::*; 20 | 21 | type HookProc = unsafe extern "system" fn(code: i32, WPARAM, LPARAM) -> LRESULT; 22 | 23 | #[derive(Debug)] 24 | struct Inner { 25 | keyboard_hook_handler: HHOOK, 26 | mouse_hook_handler: HHOOK, 27 | event_sender: EventSender, 28 | join_handle: JoinHandle<()>, 29 | thread_id: u32, 30 | } 31 | 32 | impl Inner { 33 | fn spawn_thread( 34 | tx: Sender<(HHOOK, HHOOK, u32)>, 35 | keyboard_hook_proc: HookProc, 36 | mouse_hook_proc: HookProc, 37 | ) -> JoinHandle<()> { 38 | thread::spawn(move || unsafe { 39 | let keyboard_hook_handler = WindowsAndMessaging::SetWindowsHookExW( 40 | WH_KEYBOARD_LL, 41 | Some(keyboard_hook_proc), 42 | HINSTANCE(0), 43 | 0, 44 | ) 45 | .expect("Failed to install keyboard hook."); 46 | 47 | let mouse_hook_handler = WindowsAndMessaging::SetWindowsHookExW( 48 | WH_MOUSE_LL, 49 | Some(mouse_hook_proc), 50 | HINSTANCE(0), 51 | 0, 52 | ) 53 | .expect("Failed to install mouse hook."); 54 | 55 | let thread_id = Threading::GetCurrentThreadId(); 56 | 57 | tx.send((keyboard_hook_handler, mouse_hook_handler, thread_id)) 58 | .unwrap(); 59 | 60 | WindowsAndMessaging::GetMessageW( 61 | &mut MaybeUninit::zeroed().assume_init(), 62 | HWND(0), 63 | 0, 64 | 0, 65 | ); 66 | }) 67 | } 68 | 69 | fn new( 70 | event_sender: EventSender, 71 | keyboard_hook_proc: HookProc, 72 | mouse_hook_proc: HookProc, 73 | ) -> Self { 74 | let (tx, rx) = mpsc::channel(); 75 | 76 | let join_handle = Self::spawn_thread(tx, keyboard_hook_proc, mouse_hook_proc); 77 | let (keyboard_hook_handler, mouse_hook_handler, thread_id) = rx.recv().unwrap(); 78 | 79 | Inner { 80 | keyboard_hook_handler, 81 | mouse_hook_handler, 82 | event_sender, 83 | join_handle, 84 | thread_id, 85 | } 86 | } 87 | 88 | fn uninstall(self) { 89 | unsafe { 90 | WindowsAndMessaging::UnhookWindowsHookEx(self.keyboard_hook_handler) 91 | .expect("Failed to uninstall keyboard hook."); 92 | 93 | WindowsAndMessaging::UnhookWindowsHookEx(self.mouse_hook_handler) 94 | .expect("Failed to uninstall mouse hook."); 95 | 96 | WindowsAndMessaging::PostThreadMessageW(self.thread_id, WM_QUIT, WPARAM(0), LPARAM(0)) 97 | .unwrap(); 98 | } 99 | self.join_handle.join().unwrap(); 100 | } 101 | } 102 | 103 | #[derive(Debug, Default)] 104 | pub(super) struct HookHandler { 105 | inner: Mutex>, 106 | } 107 | 108 | impl HookHandler { 109 | pub(super) fn new() -> Self { 110 | Self::default() 111 | } 112 | 113 | pub(super) fn install( 114 | &self, 115 | event_sender: EventSender, 116 | keyboard_hook_proc: HookProc, 117 | mouse_hook_proc: HookProc, 118 | ) { 119 | let mut hook = self.inner.lock().unwrap(); 120 | assert!(hook.is_none(), "Hooks are already installed."); 121 | 122 | *hook = Some(Inner::new( 123 | event_sender, 124 | keyboard_hook_proc, 125 | mouse_hook_proc, 126 | )); 127 | } 128 | 129 | pub(super) fn uninstall(&self) { 130 | self.inner 131 | .lock() 132 | .unwrap() 133 | .take() 134 | .expect("Hooks are not installed.") 135 | .uninstall(); 136 | } 137 | 138 | fn send_event(&self, event: Event) -> NativeEventOperation { 139 | self.inner 140 | .lock() 141 | .unwrap() 142 | .as_ref() 143 | .expect("Hooks are not installed.") 144 | .event_sender 145 | .send(event) 146 | } 147 | } 148 | 149 | pub(super) fn create_keyboard_event(hook: &KBDLLHOOKSTRUCT) -> Option { 150 | if hook.dwExtraInfo & SHOULD_BE_IGNORED_FLAG != 0 { 151 | return None; 152 | } 153 | let action = if hook.flags.0 >> 7 == 0 { 154 | ButtonAction::Press 155 | } else { 156 | ButtonAction::Release 157 | }; 158 | Some(ButtonEvent { 159 | target: vkcode::into_button(VIRTUAL_KEY(hook.vkCode as u16))?, 160 | injected: hook.dwExtraInfo & INJECTED_FLAG != 0, 161 | action, 162 | }) 163 | } 164 | 165 | #[inline] 166 | fn common_hook_proc_inner(hook_handler: &HookHandler, event: Event) -> NativeEventOperation { 167 | if let Event::Button(ButtonEvent { target, action, .. }) = event { 168 | match action { 169 | ButtonAction::Press => target.assume_pressed(), 170 | ButtonAction::Release => target.assume_released(), 171 | } 172 | } 173 | hook_handler.send_event(event) 174 | } 175 | 176 | #[inline] 177 | pub(super) fn keyboard_hook_proc_inner( 178 | hook_handler: &HookHandler, 179 | n_code: i32, 180 | l_param: LPARAM, 181 | ) -> NativeEventOperation { 182 | if n_code < 0 { 183 | return NativeEventOperation::Dispatch; 184 | } 185 | let hook_struct = unsafe { *(l_param.0 as *const KBDLLHOOKSTRUCT) }; 186 | let event = match create_keyboard_event(&hook_struct) { 187 | None => return NativeEventOperation::Dispatch, 188 | Some(event) => event, 189 | }; 190 | 191 | let native_operation = common_hook_proc_inner(hook_handler, Event::Button(event)); 192 | if event.action == ButtonAction::Release { 193 | return NativeEventOperation::Dispatch; 194 | } 195 | native_operation 196 | } 197 | 198 | #[derive(Debug)] 199 | enum MouseEventTarget { 200 | Button(Button), 201 | Cursor, 202 | Wheel, 203 | } 204 | 205 | fn into_mouse_event_target(w_param: WPARAM, hook: &MSLLHOOKSTRUCT) -> Option { 206 | let mouse_button = match w_param.0 as u32 { 207 | WM_MOUSEWHEEL => return Some(MouseEventTarget::Wheel), 208 | WM_MOUSEMOVE => return Some(MouseEventTarget::Cursor), 209 | WM_LBUTTONDOWN | WM_LBUTTONUP => Button::LeftButton, 210 | WM_RBUTTONDOWN | WM_RBUTTONUP => Button::RightButton, 211 | WM_MBUTTONDOWN | WM_MBUTTONUP => Button::MiddleButton, 212 | WM_XBUTTONDOWN | WM_XBUTTONUP if hook.mouseData.0 >> 16 == XBUTTON1.0 => { 213 | Button::SideButton1 214 | } 215 | WM_XBUTTONDOWN | WM_XBUTTONUP if hook.mouseData.0 >> 16 == XBUTTON2.0 => { 216 | Button::SideButton2 217 | } 218 | _ => return None, 219 | }; 220 | Some(MouseEventTarget::Button(mouse_button)) 221 | } 222 | 223 | fn into_mouse_button_action(w_param: WPARAM) -> Option { 224 | match w_param.0 as u32 { 225 | WM_LBUTTONDOWN | WM_RBUTTONDOWN | WM_MBUTTONDOWN | WM_XBUTTONDOWN => { 226 | Some(ButtonAction::Press) 227 | } 228 | WM_LBUTTONUP | WM_RBUTTONUP | WM_MBUTTONUP | WM_XBUTTONUP => Some(ButtonAction::Release), 229 | _ => None, 230 | } 231 | } 232 | 233 | fn create_mouse_event(input: &Input, w_param: WPARAM, hook: MSLLHOOKSTRUCT) -> Option { 234 | if hook.dwExtraInfo & SHOULD_BE_IGNORED_FLAG != 0 { 235 | return None; 236 | } 237 | let injected = hook.dwExtraInfo & INJECTED_FLAG != 0; 238 | let event = match into_mouse_event_target(w_param, &hook)? { 239 | MouseEventTarget::Wheel => { 240 | let delta = (hook.mouseData.0 as i32 >> 16) / WHEEL_DELTA as i32; 241 | Event::Wheel(WheelEvent { delta, injected }) 242 | } 243 | MouseEventTarget::Cursor => { 244 | let prev = input.cursor_position(); 245 | let current = hook.pt; 246 | let delta = (current.x - prev.0, current.y - prev.1); 247 | Event::Cursor(CursorEvent { delta, injected }) 248 | } 249 | MouseEventTarget::Button(button) => Event::Button(ButtonEvent { 250 | target: button, 251 | action: into_mouse_button_action(w_param)?, 252 | injected, 253 | }), 254 | }; 255 | Some(event) 256 | } 257 | 258 | #[inline] 259 | pub(super) fn mouse_hook_proc_inner( 260 | hook_handler: &HookHandler, 261 | input: &Input, 262 | n_code: i32, 263 | w_param: WPARAM, 264 | l_param: LPARAM, 265 | ) -> NativeEventOperation { 266 | if n_code < 0 { 267 | return NativeEventOperation::Dispatch; 268 | } 269 | let hook_struct = unsafe { *(l_param.0 as *const MSLLHOOKSTRUCT) }; 270 | let event = match create_mouse_event(input, w_param, hook_struct) { 271 | None => return NativeEventOperation::Dispatch, 272 | Some(event) => event, 273 | }; 274 | common_hook_proc_inner(hook_handler, event) 275 | } 276 | -------------------------------------------------------------------------------- /hookmap-core/src/sys/windows/input.rs: -------------------------------------------------------------------------------- 1 | use super::{vkcode, INJECTED_FLAG, SHOULD_BE_IGNORED_FLAG}; 2 | use crate::button::{Button, ButtonAction, ButtonKind}; 3 | 4 | use std::{mem::MaybeUninit, sync::Mutex}; 5 | 6 | use windows::Win32::UI::Input::KeyboardAndMouse; 7 | use windows::Win32::UI::WindowsAndMessaging; 8 | // For many constants. 9 | use windows::Win32::UI::Input::KeyboardAndMouse::*; 10 | use windows::Win32::UI::WindowsAndMessaging::*; 11 | 12 | const INPUT_MEM_SIZE: i32 = std::mem::size_of::() as i32; 13 | 14 | #[inline] 15 | fn create_dw_extra_info(recursive: bool) -> usize { 16 | INJECTED_FLAG | if recursive { 0 } else { SHOULD_BE_IGNORED_FLAG } 17 | } 18 | 19 | fn create_mouse_input(mouse_data: i32, dw_flags: MOUSE_EVENT_FLAGS, recursive: bool) -> INPUT { 20 | let input = MOUSEINPUT { 21 | dx: 0, 22 | dy: 0, 23 | mouseData: mouse_data, 24 | dwFlags: dw_flags, 25 | time: 0, 26 | dwExtraInfo: create_dw_extra_info(recursive), 27 | }; 28 | INPUT { 29 | r#type: INPUT_MOUSE, 30 | Anonymous: INPUT_0 { mi: input }, 31 | } 32 | } 33 | 34 | fn create_input_struct(button: Button, action: ButtonAction, recursive: bool) -> INPUT { 35 | match button.kind() { 36 | ButtonKind::Key => { 37 | let flags = match action { 38 | ButtonAction::Press => KEYBD_EVENT_FLAGS(0), 39 | ButtonAction::Release => KEYEVENTF_KEYUP, 40 | }; 41 | let keybd_input = KEYBDINPUT { 42 | wVk: vkcode::from_button(button), 43 | wScan: 0, 44 | dwFlags: flags, 45 | time: 0, 46 | dwExtraInfo: create_dw_extra_info(recursive), 47 | }; 48 | INPUT { 49 | r#type: INPUT_KEYBOARD, 50 | Anonymous: INPUT_0 { ki: keybd_input }, 51 | } 52 | } 53 | ButtonKind::Mouse => { 54 | let (mouse_data, dw_flags) = match action { 55 | ButtonAction::Press => match button { 56 | Button::LeftButton => (MOUSEHOOKSTRUCTEX_MOUSE_DATA(0), MOUSEEVENTF_LEFTDOWN), 57 | Button::RightButton => (MOUSEHOOKSTRUCTEX_MOUSE_DATA(0), MOUSEEVENTF_RIGHTDOWN), 58 | Button::MiddleButton => { 59 | (MOUSEHOOKSTRUCTEX_MOUSE_DATA(0), MOUSEEVENTF_MIDDLEDOWN) 60 | } 61 | Button::SideButton1 => (XBUTTON1, MOUSEEVENTF_XDOWN), 62 | Button::SideButton2 => (XBUTTON2, MOUSEEVENTF_XDOWN), 63 | _ => unreachable!(), 64 | }, 65 | ButtonAction::Release => match button { 66 | Button::LeftButton => (MOUSEHOOKSTRUCTEX_MOUSE_DATA(0), MOUSEEVENTF_LEFTUP), 67 | Button::RightButton => (MOUSEHOOKSTRUCTEX_MOUSE_DATA(0), MOUSEEVENTF_RIGHTUP), 68 | Button::MiddleButton => (MOUSEHOOKSTRUCTEX_MOUSE_DATA(0), MOUSEEVENTF_MIDDLEUP), 69 | Button::SideButton1 => (XBUTTON1, MOUSEEVENTF_XUP), 70 | Button::SideButton2 => (XBUTTON2, MOUSEEVENTF_XUP), 71 | _ => unreachable!(), 72 | }, 73 | }; 74 | create_mouse_input(mouse_data.0 as i32, dw_flags, recursive) 75 | } 76 | } 77 | } 78 | 79 | #[inline] 80 | fn get_cursor_position() -> (i32, i32) { 81 | unsafe { 82 | let mut pos = MaybeUninit::zeroed().assume_init(); 83 | WindowsAndMessaging::GetCursorPos(&mut pos); 84 | (pos.x, pos.y) 85 | } 86 | } 87 | 88 | #[derive(Debug)] 89 | pub(super) struct Input { 90 | cursor_position: Mutex<(i32, i32)>, 91 | } 92 | 93 | impl Input { 94 | pub(super) fn new() -> Self { 95 | Self { 96 | cursor_position: Mutex::new(get_cursor_position()), 97 | } 98 | } 99 | 100 | pub(super) fn button_input(&self, button: Button, action: ButtonAction, recursive: bool) { 101 | unsafe { 102 | KeyboardAndMouse::SendInput( 103 | &[create_input_struct(button, action, recursive)], 104 | INPUT_MEM_SIZE, 105 | ); 106 | } 107 | } 108 | 109 | pub(super) fn rotate_wheel(&self, speed: i32, recursive: bool) { 110 | let speed = speed * WHEEL_DELTA as i32; 111 | let input = create_mouse_input(speed, MOUSEEVENTF_WHEEL, recursive); 112 | unsafe { 113 | KeyboardAndMouse::SendInput(&[input], INPUT_MEM_SIZE); 114 | } 115 | } 116 | 117 | pub(super) fn cursor_position(&self) -> (i32, i32) { 118 | get_cursor_position() 119 | } 120 | 121 | pub(super) fn update_cursor_position(&self) { 122 | *self.cursor_position.lock().unwrap() = get_cursor_position(); 123 | } 124 | 125 | // FIXME: Since the cursor is moved with SetCursorPos, it cannot be hooked 126 | // with SetWindowHookEx. Therefore, recursive mouse movement is not possible. 127 | pub(super) fn move_absolute(&self, x: i32, y: i32, recursive: bool) { 128 | unsafe { 129 | // The SendInput function moves the cursor to an incorrect position, so 130 | // SetCursorPos is used to move it. 131 | WindowsAndMessaging::SetCursorPos(x, y); 132 | 133 | self.update_cursor_position(); 134 | 135 | // In some applications, the SetCursorPos function alone is not enough 136 | // to notice the cursor move, so the SendInput function is used to move 137 | // the cursor by 0. 138 | let input = create_mouse_input(0, MOUSEEVENTF_MOVE, recursive); 139 | KeyboardAndMouse::SendInput(&[input], INPUT_MEM_SIZE); 140 | } 141 | } 142 | 143 | pub(super) fn move_relative(&self, dx: i32, dy: i32, recursive: bool) { 144 | let current_pos = get_cursor_position(); 145 | let (x, y) = (current_pos.0 + dx, current_pos.1 + dy); 146 | self.move_absolute(x, y, recursive); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /hookmap-core/src/sys/windows/vkcode.rs: -------------------------------------------------------------------------------- 1 | use crate::button::Button; 2 | 3 | use windows::Win32::UI::Input::KeyboardAndMouse::VIRTUAL_KEY; 4 | 5 | pub(super) const fn into_button(vkcode: VIRTUAL_KEY) -> Option