1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#![cfg(windows)]
#![warn(
    unsafe_op_in_unsafe_fn,
    missing_docs,
    missing_debug_implementations,
    missing_copy_implementations,
    rust_2018_idioms,
    clippy::todo,
    clippy::manual_assert,
    clippy::must_use_candidate,
    clippy::inconsistent_struct_constructor,
    clippy::wrong_self_convention,
    clippy::new_without_default,
    rustdoc::broken_intra_doc_links,
    rustdoc::private_intra_doc_links
)]
#![allow(
    clippy::module_inception,
    clippy::module_name_repetitions,
    clippy::missing_errors_doc,
    clippy::borrow_as_ptr
)]

//! 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).
//!
//! For the type of events that can be hooked, see [`WindowEvent`](crate::WindowEvent).
//!
//! # Example
//! This example shows how to listen for all window events and print them to the console.
//! ```rust no_run
//! use wineventhook::{EventFilter, WindowEventHook};
//!
//! #[tokio::main]
//! async fn main() {
//!     // Create a new hook
//!     let (event_tx, mut event_rx) = tokio::sync::mpsc::unbounded_channel();
//!     let hook = WindowEventHook::hook(
//!         EventFilter::default(),
//!         event_tx,
//!     ).await.unwrap();
//!     
//!     // Wait and print events
//!     while let Some(event) = event_rx.recv().await {
//!         println!("{:#?}", event);
//!     }
//!     
//!     // Unhook the hook
//!     hook.unhook().await.unwrap();
//! }
//! ```

pub(crate) mod message_loop;
/// Module containing the raw event codes and ranges.
pub mod raw_event;

mod event;
pub use event::*;

mod hook;
pub use hook::*;

#[cfg(test)]
mod tests {
    use std::{ptr::NonNull, time::Instant};

    use winapi::um::processthreadsapi::GetCurrentThreadId;

    use crate::{
        message_loop::MessageOnlyWindow, raw_event, AccessibleObjectId, EventFilter, MaybeKnown,
        ObjectWindowEvent, WindowEventHook, WindowEventType,
    };

    #[tokio::test]
    async fn recv_object_create_on_window_create() {
        let (event_tx, mut event_rx) = tokio::sync::mpsc::unbounded_channel();
        let hook = WindowEventHook::hook(
            EventFilter::default()
                .event(raw_event::OBJECT_CREATE)
                .skip_own_process(false)
                .skip_own_thread(false),
            event_tx,
        )
        .await
        .unwrap();

        let window = MessageOnlyWindow::new().unwrap();
        let window_thread_id = unsafe { GetCurrentThreadId() };

        while let Some(event) = event_rx.recv().await {
            if event.event_type()
                == WindowEventType::Object(MaybeKnown::Known(ObjectWindowEvent::Create))
                && event.thread_id() == window_thread_id
            {
                assert_eq!(event.window_handle(), NonNull::new(window.handle()));
                assert_eq!(event.child_id(), None);
                assert_eq!(
                    event.object_type(),
                    MaybeKnown::Known(AccessibleObjectId::Window)
                );
                assert!(event.timestamp() <= Instant::now());
                break;
            }
        }

        hook.unhook().await.unwrap();
    }

    #[tokio::test]
    async fn can_unhook_with_unread_events() {
        let (event_tx, _event_rx) = tokio::sync::mpsc::unbounded_channel();
        let hook = WindowEventHook::hook(EventFilter::default(), event_tx)
            .await
            .unwrap();

        // generate some events
        for _ in 0..100 {
            MessageOnlyWindow::new().unwrap().destroy().unwrap();
        }

        hook.unhook().await.unwrap();
    }

    #[tokio::test]
    async fn can_have_multiple_hooks() {
        let (event_tx, _event_rx) = tokio::sync::mpsc::unbounded_channel();
        let hook1 = WindowEventHook::hook(EventFilter::default(), event_tx.clone())
            .await
            .unwrap();
        let hook2 = WindowEventHook::hook(EventFilter::default(), event_tx)
            .await
            .unwrap();

        // generate some events
        for _ in 0..100 {
            MessageOnlyWindow::new().unwrap().destroy().unwrap();
        }

        hook1.unhook().await.unwrap();
        hook2.unhook().await.unwrap();
    }
}