Skip to main content

wineventhook/
lib.rs

1#![cfg(windows)]
2#![warn(
3    unsafe_op_in_unsafe_fn,
4    missing_docs,
5    missing_debug_implementations,
6    missing_copy_implementations,
7    rust_2018_idioms,
8    clippy::pedantic,
9    clippy::todo,
10    clippy::manual_assert,
11    clippy::must_use_candidate,
12    clippy::inconsistent_struct_constructor,
13    clippy::wrong_self_convention,
14    clippy::new_without_default,
15    rustdoc::broken_intra_doc_links,
16    rustdoc::private_intra_doc_links
17)]
18#![allow(
19    clippy::module_inception,
20    clippy::module_name_repetitions,
21    clippy::missing_errors_doc,
22    clippy::borrow_as_ptr,
23    clippy::cast_sign_loss,
24    clippy::missing_panics_doc,
25    clippy::doc_markdown,
26    clippy::cast_possible_wrap,
27    clippy::cast_possible_truncation
28)]
29
30//! A rusty wrapper over the window event API specifically [`SetWinEventHook`](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwineventhook) and [`UnhookWinEvent`](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-unhookwinevent).
31//!
32//! For the type of events that can be hooked, see [`WindowEvent`].
33//!
34//! # Example
35//! This example shows how to listen for all window events and print them to the console.
36//! ```rust no_run
37//! use wineventhook::{EventFilter, WindowEventHook};
38//!
39//! #[tokio::main]
40//! async fn main() {
41//!     // Create a new hook
42//!     let (event_tx, mut event_rx) = tokio::sync::mpsc::unbounded_channel();
43//!     let hook = WindowEventHook::hook(
44//!         EventFilter::default(),
45//!         event_tx,
46//!     ).await.unwrap();
47//!     
48//!     // Wait and print events
49//!     while let Some(event) = event_rx.recv().await {
50//!         println!("{:#?}", event);
51//!     }
52//!     
53//!     // Unhook the hook
54//!     hook.unhook().await.unwrap();
55//! }
56//! ```
57
58pub(crate) mod message_loop;
59/// Module containing the raw event codes and ranges.
60pub mod raw_event;
61
62mod event;
63pub use event::*;
64
65mod hook;
66pub use hook::*;
67
68#[cfg(test)]
69mod tests {
70    use std::{ptr::NonNull, time::Instant};
71
72    use windows_sys::Win32::System::Threading::GetCurrentThreadId;
73
74    use crate::{
75        AccessibleObjectId, EventFilter, ObjectWindowEvent, WindowEventHook, WindowEventType,
76        message_loop::MessageOnlyWindow, raw_event,
77    };
78
79    #[tokio::test]
80    async fn recv_object_create_on_window_create() {
81        let (event_tx, mut event_rx) = tokio::sync::mpsc::unbounded_channel();
82        let hook = WindowEventHook::hook(
83            EventFilter::default()
84                .event(raw_event::OBJECT_CREATE)
85                .skip_own_process(false)
86                .skip_own_thread(false),
87            event_tx,
88        )
89        .await
90        .unwrap();
91
92        let window = MessageOnlyWindow::new().unwrap();
93        let window_thread_id = unsafe { GetCurrentThreadId() };
94
95        while let Some(event) = event_rx.recv().await {
96            if event.event_type() == WindowEventType::Object(ObjectWindowEvent::Create)
97                && event.thread_id() == window_thread_id
98            {
99                assert_eq!(event.window_handle(), NonNull::new(window.handle()));
100                assert_eq!(event.child_id(), None);
101                assert_eq!(event.object_type(), AccessibleObjectId::Window);
102                assert!(event.timestamp() <= Instant::now());
103                break;
104            }
105        }
106
107        hook.unhook().await.unwrap();
108    }
109
110    #[tokio::test]
111    async fn can_unhook_with_unread_events() {
112        let (event_tx, _event_rx) = tokio::sync::mpsc::unbounded_channel();
113        let hook = WindowEventHook::hook(EventFilter::default(), event_tx)
114            .await
115            .unwrap();
116
117        // generate some events
118        for _ in 0..100 {
119            MessageOnlyWindow::new().unwrap().destroy().unwrap();
120        }
121
122        hook.unhook().await.unwrap();
123    }
124
125    #[tokio::test]
126    async fn can_have_multiple_hooks() {
127        let (event_tx, _event_rx) = tokio::sync::mpsc::unbounded_channel();
128        let hook1 = WindowEventHook::hook(EventFilter::default(), event_tx.clone())
129            .await
130            .unwrap();
131        let hook2 = WindowEventHook::hook(EventFilter::default(), event_tx)
132            .await
133            .unwrap();
134
135        // generate some events
136        for _ in 0..100 {
137            MessageOnlyWindow::new().unwrap().destroy().unwrap();
138        }
139
140        hook1.unhook().await.unwrap();
141        hook2.unhook().await.unwrap();
142    }
143}