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
use std::{fmt::Debug, hash::Hash};

pub use config::Config;
use errors::{Error, Result};
pub use handler::EventHandler;
use hook::{ThreadedInner, UnthreadedInner, WinEventHookInner};
use tracing::trace;
use windows::Win32::UI::Accessibility::HWINEVENTHOOK;

pub mod config;
pub mod errors;
mod event_loop;
pub mod events;
pub mod flags;
pub mod handler;
mod hook;

/// Re-exported [`HWINEVENTHOOK`].
pub type OsHandle = HWINEVENTHOOK;

/// A Windows Event Hook, managed using the
/// [SetWinEventHook](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwineventhook)
/// and
/// [UnhookWinEvent](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-unhookwinevent)
/// Windows API functions.
pub struct WinEventHook {
    inner: Box<dyn WinEventHookInner>,
}

impl WinEventHook {
    /// Obtains a reference to the os-specific handle of the event hook.
    pub fn os_handle(&self) -> &Option<OsHandle> {
        self.inner.handle()
    }

    /// Determines if the hook is currently installed.
    pub fn installed(&self) -> bool {
        self.inner.installed()
    }

    /// Installs a hook, using a given [`Config`] and [`EventHandler`] function.
    ///
    /// Note: [`Config`] can be created using the builder pattern, with [`Config::builder`].
    pub fn install<F: EventHandler + 'static>(config: Config, handler: F) -> Result<Self> {
        trace!(?config, "validating config");

        if !config.is_valid() {
            return Err(Error::InvalidConfig(config));
        }

        trace!("config valid, attempting to install hook");

        Ok(Self {
            inner: match config.dedicated_thread_name.is_none() {
                true => Box::new(UnthreadedInner::new(config, Box::new(handler))?),
                false => Box::new(ThreadedInner::new(config, Box::new(handler))?),
            },
        })
    }

    /// Uninstalls a hook, if it is not currently installed.
    pub fn uninstall(&mut self) -> Result<()> {
        self.inner.uninstall()
    }
}

impl Debug for WinEventHook {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("WinEventHook")
            .field("os_handle", self.os_handle())
            .finish()
    }
}

impl PartialEq for WinEventHook {
    fn eq(&self, other: &Self) -> bool {
        self.os_handle().eq(other.os_handle())
    }
}

impl Eq for WinEventHook {}

impl Hash for WinEventHook {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        let maybe_os_handle = self.os_handle().and_then(|handle| Some(handle.0));

        maybe_os_handle.hash(state)
    }
}

#[cfg(test)]
mod tests {

    use tracing::info;
    use tracing_subscriber::{EnvFilter, FmtSubscriber};

    use super::{
        events::{Event, NamedEvent},
        Config, WinEventHook,
    };

    #[test]
    fn can_install_threaded() {
        let subscriber = FmtSubscriber::builder()
            .with_env_filter(EnvFilter::from_default_env())
            .finish();

        tracing::subscriber::set_global_default(subscriber).unwrap();

        let cfg = Config::builder()
            .with_event(Event::Named(NamedEvent::ObjectShow))
            .with_event(Event::Named(NamedEvent::ObjectNameChange))
            .with_event(Event::Named(NamedEvent::ObjectHide))
            .with_dedicated_thread()
            .finish();

        let mut hook =
            WinEventHook::install(cfg, |ev, _, _, _, _, _| info!(?ev, "got event")).unwrap();

        hook.uninstall().unwrap();
    }
}