willhook/
lib.rs

1//! # What this crate provides
2//! 
3//! This Windows-only crate provides safe and correct means to listen for keyboard and mouse events regardless of application focus.
4//! The application can be CLI or with a Window.
5//! 
6//! Under the hood the crate leverages the **WI**ndows **L**ow-**L**evel **HOOK**s.
7//! You can read more about that topic on [MSDN](https://learn.microsoft.com/en-us/windows/win32/winmsg/about-hooks?redirectedfrom=MSDN).
8//!
9//! The crate was created for learning-purposes mostly and for my hobby project, but we will see where it goes.
10//! 
11//! The design goals for this crate are to be: correct, misuse-proof and fail-proof.
12//! Having that in mind, the implementation follows best effort to avoid any panic.
13//! In the worst case, it should just return incomplete input event (e.g. with missing keyboard key code).
14//! 
15//! ### What this crate does NOT provide
16//! 
17//! This crate is intended for "read-only" access to hooks. It does not support injecting input events or altering them.
18//! If you are looking for that kind of functionality, you can give [mki](https://crates.io/crates/mki) a try.
19//! In comparison, the mki crate supports also Linux, but does not cleanup the low-level hooks (by [unhooking them](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-unhookwindowshookex)) and threads behind them (by [joinging with them](https://doc.rust-lang.org/std/thread/struct.JoinHandle.html#method.join)).
20//! This *may* not be an issue for you. The addition of "injecting" and "altering" input events to [willhook] is a possibility, although it is not top priority.
21//! 
22//! # Warning: The current state
23//! 
24//! Currently it supports listening to mouse and keyboard actions, see [event] module for details.
25//! There is no fancy logic to interpret the events - with this crate you can just received them and do what you want with that information.
26//! In that aspect, I consider it feature complete.
27//! There are integration tests that should cover all realistic scenarios.
28//! There are also some unit tests covering less realistic cases, when Windows OS would send invalid input.
29//! I think the crate is rather well tested, but still, keep in mind that the crate is also "young".
30//! Note: the integration tests inject mouse and keyboard events, also they need to be run sequentially (no multi-threading). 
31//! There are some tests that do not pass on GitHub Actions and are ignored.
32//! With that in mind, run the tests with `cargo test --tests -- --test-threads=1 --include-ignored`.
33//! *It is highly recommended to at least quickly review the code before using this crate for anything more then hobby projects, at least at the current state.*
34//! 
35//! TODO:
36//! - document unsafe code before I forget all the quirks :-)
37//! - maybe write more unit tests
38//! - maybe improve the crate partitioning to modules (without breaking the API)
39//! - maybe rework underlying channels, so that they are dropped with the hook (now they are just drained)
40//! - maybe add injecting events
41//! - maybe add blocking events, if even possible
42//! - maybe add manipulating events, if even possible
43//! - maybe add information about time of the event
44//! 
45//! # How it works
46//! 
47//! In short, there are a few handy functions to request a hook: [keyboard_hook], [mouse_hook] and [willhook].
48//! When called they:
49//! - start background thread(s) for each low-level hook, and in that thread(s):
50//!     - register a mouse and/or keyboard low-level hook(s)
51//!     - start Windows message queue and wait for the message to end execution
52//! - create, if were not created already, the channels for passing events to "client" thread
53//! - return the handle to the underlying low-level hooks as [hook::Hook]
54//! 
55//! When the [hook::Hook] goes out of scope, the underlying resources supporting low-level hooks are dropped:
56//! - each of the underlying low-level hooks is unhooked from the Windows Kernel
57//! - each of the background threads is properly joined
58//! - all pending events are dropped (background channels are drained)
59//! 
60//! When the [hook::Hook] is active (in scope / not dropped). 
61//! Then one can receive recorded [event::InputEvent]s via [hook::Hook::try_recv].
62//! It works similiarly to [std::sync::mpsc::Receiver::try_recv].
63//! 
64//! # Optional features
65//! 
66//! ## Serde support
67//! 
68//! To enable [serde](https://crates.io/crates/serde) support, add willhook with "serde" feature to your cargo.toml:
69//! 
70//! `willhook = { version = "^0.6.2", features = ["serde"]}`
71
72pub mod hook;
73pub mod event;
74
75pub use hook::Hook;
76pub use hook::HookBuilder;
77pub use event::*;
78
79/// Return the Keyboard Hook handle. For more details see [Hook] and [HookBuilder]
80pub fn keyboard_hook() -> Option<Hook> {
81    HookBuilder::new().with_keyboard().build()
82}
83
84/// Return the Mouse Hook handle. For more details see [Hook] and [HookBuilder]
85pub fn mouse_hook() -> Option<Hook> {
86    HookBuilder::new().with_mouse().build()
87}
88
89/// Return the handle for both mouse and keyboard hook. For more details see [Hook] and [HookBuilder]
90pub fn willhook() -> Option<Hook> {
91    HookBuilder::new().with_keyboard().with_mouse().build()
92}