Reputation: 57
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
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 ashmod
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 like0x00000000
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