hwnd 0.0.0-2024-01-05

Well documented, safe-leaning, sound, low-level API bindings to `HWND`-adjacent APIs
Documentation
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
#![forbid(unsafe_op_in_unsafe_fn)]

use hwnd::*;

use abistr::cstr16;

use bytemuck::*;

use winapi::um::winuser::*;

use winresult::ERROR;

use std::ptr::*;
use std::sync::atomic::{AtomicBool, Ordering::Relaxed};



fn main() {
    // To ensure any Drop s are run before calling exit, dispatch
    // all logic to an inner implementation function:
    std::process::exit(main_imp())
}

fn main_imp() -> i32 {
    let hinstance = get_module_handle_entry_exe().unwrap();
    let hcursor = load_cursor_w(None, IDC::ARROW).unwrap();
    let hicon   = load_icon_w(None, IDI::APPLICATION).unwrap();

    let wc = WndClassW {
        wnd_proc: Some(window_proc),
        hinstance,
        hcursor,
        hicon,
        class_name: cstr16!("SampleWndClass").into(),
        .. WndClassW::zeroed()
    };
    let wc = unsafe { register_class_w(&wc) }.unwrap();

    let ex_style = 0;
    let style = WS::OVERLAPPEDWINDOW;
    let size = adjust_window_rect_ex_copy(
        Rect { left: 0, right: 800, top: 0, bottom: 600 },
        style, false, ex_style
    ).unwrap();

    let hwnd = unsafe { create_window_ex_w(
        ex_style,
        wc,
        cstr16!("hello-world"),
        style,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        size.right - size.left,
        size.bottom - size.top,
        null_mut(),
        null_mut(),
        hinstance,
        null_mut(),
    )}.unwrap();

    show_window_async(hwnd, SW::SHOWNORMAL).unwrap();

    let mut msg = Msg::zeroed();
    while get_message_w(&mut msg, HWnd::NULL, 0, 0).unwrap() {
        translate_message(&msg);
        let _ = unsafe { dispatch_message_w(&msg) };
    }

    if msg.message == WM::QUIT { return msg.wparam as _ }
    0 // assume success of we ever `break` out of our message loop instead of `return !0`ing
}

/// ### ⚠️ Safety ⚠️
/// *   `hwnd` must be a valid window
/// *   `wparam` / `lparam` may be assumed to be valid pointers depending no the exact `umsg` passed
unsafe extern "system" fn window_proc(hwnd: HWnd, umsg: WM32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
    if umsg == WM::GETMINMAXINFO {
        EARLY.set(hwnd, "early").unwrap();
        LATE .set(hwnd, "late" ).unwrap();
    }

    eprintln!("window_proc({hwnd:?}, {umsg:?}");
    eprintln!("  early: {:?}", EARLY.get_copy(hwnd));
    eprintln!("  late:  {:?}", LATE .get_copy(hwnd));

    match umsg {
        WM::LBUTTONDOWN => {
            // This blocks, without preventing `hwnd` from being closed, allowing me
            // to experiment with yanking HWNDs out from under recursive wndprocs.
            unsafe { MessageBoxA(null_mut(), "Message Box\0".as_ptr().cast(), "Caption\0".as_ptr().cast(), MB_OK) };
            0
        },
        WM::RBUTTONDOWN => {
            unsafe { destroy_window(hwnd) }.unwrap();
            assert_eq!(ERROR::INVALID_WINDOW_HANDLE, unsafe { destroy_window(hwnd) }.unwrap_err());
            0
        },
        WM::DESTROY => {
            assert!(is_window(hwnd));
            assert_eq!(ERROR::INVALID_WINDOW_HANDLE, EARLY.set(hwnd, "early").unwrap_err());    // unable to set "early" data: window is being destroyed
            LATE.set(hwnd, "late").unwrap();                                                    // still able to set "late" data

            static DESTROY : AtomicBool = AtomicBool::new(false);
            if !DESTROY.load(Relaxed) {
                DESTROY.store(true, Relaxed);
                unsafe { destroy_window(hwnd) }.unwrap();
                assert_eq!(ERROR::INVALID_WINDOW_HANDLE, LATE.set(hwnd, "late").unwrap_err());  // no longer able to set even "late" data: destroy_window returned
            }
            post_quit_message(0);
            0
        },
        _ => unsafe { def_window_proc_w(hwnd, umsg, wparam, lparam) },
    }
}

static EARLY : assoc::local::Slot<&'static str> = assoc::local::Slot::new_drop_early();
static LATE  : assoc::local::Slot<&'static str> = assoc::local::Slot::new_drop_late();