wineventhook 0.11.0

A rusty wrapper over SetWinEventHook and UnhookWinEvent.
Documentation
use std::{
    io,
    num::NonZeroUsize,
    ptr::{self, NonNull},
};

use windows_sys::Win32::{
    Foundation::{GetLastError, HWND, SetLastError},
    UI::WindowsAndMessaging::{GetWindowTextLengthW, GetWindowTextW},
};
use wineventhook::{AccessibleObjectId, EventFilter, WindowEventHook, raw_event};

#[tokio::main]
async fn main() {
    let (event_tx, mut event_rx) = tokio::sync::mpsc::unbounded_channel();
    let hook = WindowEventHook::hook(
        EventFilter::default().event(raw_event::SYSTEM_FOREGROUND),
        event_tx,
    )
    .await
    .unwrap();

    while let Some(event) = event_rx.recv().await {
        if event.object_type() == AccessibleObjectId::Window {
            let title = get_window_text(
                event
                    .window_handle()
                    .map_or_else(ptr::null_mut, NonNull::as_ptr),
            )
            .unwrap();
            println!("{:?}", title);
        }
    }

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

fn get_window_text_length(window: HWND) -> io::Result<Option<NonZeroUsize>> {
    unsafe { SetLastError(0) };
    let result = unsafe { GetWindowTextLengthW(window) };
    if result == 0 && unsafe { GetLastError() } != 0 {
        Err(io::Error::last_os_error())
    } else {
        Ok(NonZeroUsize::new(result as usize))
    }
}

fn get_window_text(window: HWND) -> io::Result<Option<String>> {
    let text_len = if let Some(length) = get_window_text_length(window)? {
        length.get()
    } else {
        return Ok(None);
    };

    let mut text = Vec::with_capacity(text_len + 1); // +1 for null terminator
    let result = unsafe { GetWindowTextW(window, text.as_mut_ptr(), text.capacity() as i32) };
    if result != 0 {
        unsafe { text.set_len(text_len) };
        let text = String::from_utf16_lossy(&text);
        Ok(Some(text))
    } else {
        Err(io::Error::last_os_error())
    }
}