use std::{
cell::RefCell,
marker::PhantomData,
mem,
pin::Pin,
ptr::{self, NonNull},
sync::Once,
};
use windows_sys::Win32::{Foundation::*, UI::WindowsAndMessaging::*};
fn get_instance_handle() -> HINSTANCE {
extern "C" {
static __ImageBase: u8;
}
unsafe { ptr::from_ref(&__ImageBase) as _ }
}
struct SubClassInformation {
wndproc: unsafe extern "system" fn(HWND, u32, WPARAM, LPARAM) -> LRESULT,
user_data: *const (),
}
#[derive(Debug, Clone)]
pub struct WindowMessage {
pub hwnd: HWND,
pub msg: u32,
pub wparam: WPARAM,
pub lparam: LPARAM,
}
#[repr(C)]
struct UserData<S, F> {
state: S,
wndproc: F,
}
#[derive(Debug)]
pub struct Window<S> {
hwnd: HWND,
_state: PhantomData<S>,
}
impl<S> Drop for Window<S> {
fn drop(&mut self) {
unsafe { DestroyWindow(self.hwnd) };
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WindowType {
TopLevel,
MessageOnly,
}
#[derive(Debug)]
pub struct WindowCreationError;
impl<S> Window<S> {
pub fn new<F>(
window_type: WindowType,
state: S,
wndproc: F,
) -> Result<Self, WindowCreationError>
where
F: Fn(Pin<&S>, WindowMessage) -> Option<LRESULT> + 'static,
{
let class_name = c"wintf-winmsg-executor".as_ptr().cast();
static CLASS_REGISTRATION: Once = Once::new();
CLASS_REGISTRATION.call_once(|| {
let mut wnd_class: WNDCLASSA = unsafe { std::mem::zeroed() };
wnd_class.lpfnWndProc = Some(wndproc_setup);
wnd_class.hInstance = get_instance_handle();
wnd_class.lpszClassName = class_name;
unsafe { RegisterClassA(&wnd_class) };
});
let subclassinfo = SubClassInformation {
wndproc: wndproc_typed::<S, F>,
user_data: Box::into_raw(Box::new(UserData { state, wndproc })).cast(),
};
let hwnd = unsafe {
CreateWindowExA(
0,
class_name,
ptr::null(),
0,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
match window_type {
WindowType::TopLevel => ptr::null_mut(),
WindowType::MessageOnly => HWND_MESSAGE,
},
ptr::null_mut(),
get_instance_handle(),
ptr::from_ref(&subclassinfo).cast(),
)
};
if hwnd.is_null() {
return Err(WindowCreationError);
}
Ok(Self {
hwnd,
_state: PhantomData,
})
}
pub fn new_checked<F>(
window_type: WindowType,
state: S,
wndproc: F,
) -> Result<Self, WindowCreationError>
where
F: FnMut(Pin<&S>, WindowMessage) -> Option<LRESULT> + 'static,
{
let wndproc = RefCell::new(wndproc);
Self::new(window_type, state, move |state, msg| {
let mut wndproc = wndproc.try_borrow_mut().ok()?;
wndproc(state, msg)
})
}
fn user_data(&self) -> &UserData<S, ()> {
unsafe { &*(GetWindowLongPtrA(self.hwnd, GWLP_USERDATA) as *const _) }
}
pub fn hwnd(&self) -> HWND {
self.hwnd
}
pub fn state(&self) -> Pin<&S> {
unsafe { Pin::new_unchecked(&self.user_data().state) }
}
}
unsafe extern "system" fn wndproc_setup(
hwnd: HWND,
msg: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
if msg == WM_NCCREATE {
let create_params = lparam as *const CREATESTRUCTA;
let subclassinfo = &*((*create_params).lpCreateParams as *const SubClassInformation);
SetWindowLongPtrA(hwnd, GWLP_WNDPROC, subclassinfo.wndproc as usize as _);
SetWindowLongPtrA(hwnd, GWLP_USERDATA, subclassinfo.user_data as _);
SendMessageA(hwnd, msg, wparam, lparam)
} else {
DefWindowProcA(hwnd, msg, wparam, lparam)
}
}
unsafe extern "system" fn wndproc_typed<S, F>(
hwnd: HWND,
msg: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT
where
F: Fn(Pin<&S>, WindowMessage) -> Option<LRESULT> + 'static,
{
let user_data_ptr: NonNull<UserData<S, F>> = if mem::size_of::<UserData<S, F>>() == 0 {
NonNull::dangling()
} else {
NonNull::new_unchecked(GetWindowLongPtrA(hwnd, GWLP_USERDATA) as _)
};
let user_data = user_data_ptr.as_ref();
let ret = (user_data.wndproc)(
Pin::new_unchecked(&user_data.state),
WindowMessage {
hwnd,
msg,
wparam,
lparam,
},
);
if msg == WM_CLOSE {
return 0;
}
if msg == WM_NCDESTROY {
drop(Box::from_raw(user_data_ptr.as_ptr()));
return 0;
}
ret.unwrap_or_else(|| DefWindowProcA(hwnd, msg, wparam, lparam))
}
#[cfg(test)]
mod test {
use crate::{FilterResult, MessageLoop};
use super::*;
use std::{
cell::Cell,
rc::{Rc, Weak},
};
#[test]
fn create_destroy_messages() {
let mut expected_messages = [WM_NCCREATE, WM_CREATE, WM_DESTROY, WM_NCDESTROY].into_iter();
let mut expected_message = expected_messages.next();
let match_cnt = Rc::new(Cell::new(0));
let w = Window::new_checked(WindowType::TopLevel, (), {
let match_cnt = match_cnt.clone();
move |_, msg| {
dbg!(msg.msg);
if msg.msg == expected_message.unwrap() {
expected_message = expected_messages.next();
match_cnt.set(match_cnt.get() + 1);
}
None
}
})
.unwrap();
assert_eq!(match_cnt.get(), 2); drop(w);
assert_eq!(match_cnt.get(), 4); }
#[test]
fn reenter_state() {
let state = RefCell::new(false);
let w = Rc::new_cyclic(move |this: &Weak<Window<RefCell<bool>>>| {
let this = this.clone();
Window::new(WindowType::MessageOnly, state, move |state, _msg| {
let mut state = state.borrow_mut();
if let Some(w) = this.upgrade() {
if w.state().try_borrow_mut().is_err() {
*state = true;
unsafe { PostMessageA(w.hwnd(), WM_USER, 0, 0) };
}
}
None
})
.unwrap()
});
unsafe { PostMessageA(w.hwnd(), WM_USER, 0, 0) };
MessageLoop::run(|msg_loop, _| {
if *w.state().borrow() {
msg_loop.quit();
}
FilterResult::Forward
});
}
}