dalvi
dalvi

Reputation: 57

SetWinEventHook not capturing any events in Rust application

I'm trying to make application with Rust that counts time spent within specific window.
And now I'm stuck with Windows hooks.

I tried to use windows crate to setup hooks, where I could not make SetWindowsHookExW to work, because it was not allowing to pass null ptr as hmod argument. And SetWindowsHookW compiles but always returns 0 and that's it. Now I switched to winapi crate, using SetWinEventHookExW retunrs something like 0x00000000 which I think is not successful operation. And SetWinEventHook when called returns seemingly normal value, but events are not captured for reason I can't figure out. Here is my current code:

// dependencies imports
...

thread_local! {
    static TX: OnceCell<Sender<RawWindowEvent>>= OnceCell::new()
}

extern "system" fn win_event_hook_callback(
    child_id: HWINEVENTHOOK,
    hook_handle: DWORD,
    event_id: HWND,
    window_handle: LONG,
    object_id: LONG,
    thread_id: DWORD,
    timestamp: DWORD,
) -> () {
    println!("EVENT?");
    TX.with(|f| {
        let tx = f.get().unwrap();
        let event = RawWindowEvent {
            child_id,
            hook_handle,
            event_id,
            window_handle,
            object_id,
            thread_id,
            timestamp,
        };
        println!("{:#?}", &event);
        tx.send(event);
    });
}

fn main() {
    println!("Hello, world!");
    let (tx, rx) = channel::<RawWindowEvent>();

    match TX.with(|f| f.set(tx)) {
        Err(err) => panic!("{:#?}", err),
        _ => (),
    };

    unsafe {
        let hook = SetWinEventHook(
            0x0003,
            0x0003,
            ptr::null_mut(),
            Some(win_event_hook_callback),
            0,
            0,
            0,
        );
        println!("{:#?}", hook);

        loop {
            let event = rx.recv().unwrap();
            println!("{:#?}", event);
        }

        UnhookWinEvent(hook);
    }
}



#[derive(Debug)]
pub struct RawWindowEvent {
    pub child_id: HWINEVENTHOOK,
    pub hook_handle: DWORD,
    pub event_id: HWND,
    pub window_handle: LONG,
    pub object_id: LONG,
    pub thread_id: DWORD,
    pub timestamp: DWORD,
}

Upvotes: 0

Views: 353

Answers (1)

IInspectable
IInspectable

Reputation: 51506

There are several questions here, so let's quickly cover those that aren't actually relevant up front:

I could not make SetWindowsHookExW to work, because it was not allowing to pass null ptr as hmod argument.

The signature for SetWindowsHookExW in the windows crate expects a type that implements IntoParam<HMODULE> for the hmod parameter, which allows passing either a valid HMODULE value, or None (which gets converted into a null pointer).

Now I switched to winapi crate

You shouldn't. It's no longer actively maintained and doesn't, to my knowledge, provide any functionality that's not also available through the windows (or windows-sys) crates.

using SetWinEventHookExW retunrs something like 0x00000000 which I think is not successful operation

I don't know what SetWinEventHookExW is. I assume this is a typo, and should read SetWindowsHookExW instead. If that is the case, a return value of zero indicates failure.


With that covered, WinEvents is the most appropriate system service to monitor foreground activation events, and windows is the most convenient Rust crate to access it. Regardless of whether you choose hooks or WinEvents, you practically always have to spin up a message loop (calling GetMessage/DispatchMessage) on the thread that installed the hook to receive notifications.

The following implementation (using the windows crate) sets up a WinEvents hook to receive EVENT_SYSTEM_FOREGROUND notifications:

main.rs

use windows::{
    w,
    Win32::{
        Foundation::HWND,
        UI::{
            Accessibility::{SetWinEventHook, HWINEVENTHOOK},
            WindowsAndMessaging::{
                MessageBoxW, EVENT_SYSTEM_FOREGROUND, MB_OK, WINEVENT_OUTOFCONTEXT,
            },
        },
    },
};

fn main() {
    let hook = unsafe {
        SetWinEventHook(
            EVENT_SYSTEM_FOREGROUND,
            EVENT_SYSTEM_FOREGROUND,
            None,
            Some(win_event_hook_callback),
            0,
            0,
            WINEVENT_OUTOFCONTEXT,
        )
    };
    // Make sure the hook is installed; a real application would want to do more
    // elaborate error handling
    assert!(!hook.is_invalid(), "Failed to install hook");

    // Have the system spin up a message loop (and get a convenient way to exit
    // the application for free)
    let _ = unsafe {
        MessageBoxW(
            None,
            w!("Click OK to terminate"),
            w!("Event hook running"),
            MB_OK,
        )
    };
}

unsafe extern "system" fn win_event_hook_callback(
    _hook_handle: HWINEVENTHOOK,
    _event_id: u32,
    _window_handle: HWND,
    _object_id: i32,
    _child_id: i32,
    _thread_id: u32,
    _timestamp: u32,
) {
    println!("Event received.");
}

Cargo.toml

[package]
name = "win_event_hook"
version = "0.0.0"
edition = "2021"

[dependencies.windows]
version = "0.48.0"
features = [
    "Win32_Foundation",
    "Win32_UI_Accessibility",
    "Win32_UI_WindowsAndMessaging",
]

This is taking advantage of the fact, that the system will dispatch messages on the calling thread as long as the MessageBoxW is up. Depending on application needs, you'd probably want to implement your own message loop. This doesn't strictly require spinning up an additional thread; MsgWaitForMultipleObjectsEx solves this by waiting for messages arriving, kernel objects getting signaled, or I/O completion routines/APCs getting queued on the calling thread.

Upvotes: 0

Related Questions