use std::{
cell::RefCell,
marker::PhantomData,
mem,
pin::Pin,
ptr::{self, NonNull},
sync::Once,
};
use windows::core::w;
use windows::Win32::{Foundation::*, UI::WindowsAndMessaging::*};
fn get_instance_handle() -> HINSTANCE {
extern "C" {
static __ImageBase: u8;
}
HINSTANCE(ptr::from_ref(unsafe { &__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) {
let _ = 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,
{
Self::new_ex(window_type, WINDOW_EX_STYLE(0), state, wndproc)
}
pub fn new_ex<F>(
window_type: WindowType,
ex_style: WINDOW_EX_STYLE,
state: S,
wndproc: F,
) -> Result<Self, WindowCreationError>
where
F: Fn(Pin<&S>, WindowMessage) -> Option<LRESULT> + 'static,
{
let class_name = w!("wintf-winmsg-executor");
static CLASS_REGISTRATION: Once = Once::new();
CLASS_REGISTRATION.call_once(|| {
let mut wnd_class: WNDCLASSW = unsafe { std::mem::zeroed() };
wnd_class.lpfnWndProc = Some(wndproc_setup);
wnd_class.hInstance = get_instance_handle();
wnd_class.lpszClassName = class_name;
unsafe { RegisterClassW(&wnd_class) };
});
let subclassinfo = SubClassInformation {
wndproc: wndproc_typed::<S, F>,
user_data: Box::into_raw(Box::new(UserData { state, wndproc })).cast(),
};
let hwnd = unsafe {
CreateWindowExW(
ex_style,
class_name,
None,
WINDOW_STYLE(0),
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
match window_type {
WindowType::TopLevel => None,
WindowType::MessageOnly => Some(HWND_MESSAGE),
},
None,
Some(get_instance_handle()),
Some(ptr::from_ref(&subclassinfo).cast()),
)
}
.map_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,
{
Self::new_checked_ex(window_type, WINDOW_EX_STYLE(0), state, wndproc)
}
pub fn new_checked_ex<F>(
window_type: WindowType,
ex_style: WINDOW_EX_STYLE,
state: S,
wndproc: F,
) -> Result<Self, WindowCreationError>
where
F: FnMut(Pin<&S>, WindowMessage) -> Option<LRESULT> + 'static,
{
let wndproc = RefCell::new(wndproc);
Self::new_ex(window_type, ex_style, state, move |state, msg| {
let mut wndproc = wndproc.try_borrow_mut().ok()?;
wndproc(state, msg)
})
}
fn user_data(&self) -> &UserData<S, ()> {
unsafe { &*(GetWindowLongPtrW(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.0 as *const CREATESTRUCTW;
let subclassinfo = &*((*create_params).lpCreateParams as *const SubClassInformation);
SetWindowLongPtrW(hwnd, GWLP_WNDPROC, subclassinfo.wndproc as usize as _);
SetWindowLongPtrW(hwnd, GWLP_USERDATA, subclassinfo.user_data as _);
SendMessageW(hwnd, msg, Some(wparam), Some(lparam))
} else {
DefWindowProcW(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(GetWindowLongPtrW(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 LRESULT(0);
}
if msg == WM_NCDESTROY {
drop(Box::from_raw(user_data_ptr.as_ptr()));
return LRESULT(0);
}
ret.unwrap_or_else(|| DefWindowProcW(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;
let _ = unsafe {
PostMessageW(Some(w.hwnd()), WM_USER, WPARAM(0), LPARAM(0))
};
}
}
None
})
.unwrap()
});
let _ = unsafe { PostMessageW(Some(w.hwnd()), WM_USER, WPARAM(0), LPARAM(0)) };
MessageLoop::run(|msg_loop, _| {
if *w.state().borrow() {
msg_loop.quit();
}
FilterResult::Forward
});
}
}