1use crate::{
2 core::{CheckNullError, CheckNumberError, ResultExt},
3 windows, Null, Zeroed,
4};
5use std::{cell::Cell, mem};
6use windows::{
7 core::{HSTRING, PCWSTR},
8 Win32::{
9 Foundation::{SetLastError, ERROR_SUCCESS, HWND, LPARAM, LRESULT, POINT, SIZE, WPARAM},
10 System::{LibraryLoader::GetModuleHandleW, Performance::QueryPerformanceCounter},
11 UI::WindowsAndMessaging::{
12 CreateWindowExW, DefWindowProcW, DestroyWindow, GetWindowLongPtrW, IsWindow,
13 RegisterClassExW, SetWindowLongPtrW, UnregisterClassW, CW_USEDEFAULT, GWLP_USERDATA,
14 HMENU, HWND_MESSAGE, WINDOW_EX_STYLE, WINDOW_STYLE, WNDCLASSEXW,
15 },
16 },
17};
18
19mod translate;
20
21pub use translate::*;
22
23thread_local! {
24 static NEXT_WINDOW_USER_DATA_ON_INIT: Cell<isize> = const { Cell::new(0) };
25}
26
27pub trait WndProc: FnMut(HWND, u32, WPARAM, LPARAM) -> Option<LRESULT> {}
29
30impl<F> WndProc for F where F: FnMut(HWND, u32, WPARAM, LPARAM) -> Option<LRESULT> {}
32
33pub struct WindowClass<'a> {
38 atom: u16,
39 wnd_proc_ptr: *mut Box<dyn WndProc + 'a>,
41}
42
43impl<'a> WindowClass<'a> {
44 pub fn new<F>(wnd_proc: F) -> windows::core::Result<Self>
45 where
46 F: WndProc + 'a,
47 {
48 Self::with_name(&Self::make_name()?, wnd_proc)
64 }
65
66 pub fn with_name<F>(name: &str, wnd_proc: F) -> windows::core::Result<Self>
67 where
68 F: WndProc + 'a,
69 {
70 Self::with_details(
71 WNDCLASSEXW {
72 cbSize: mem::size_of::<WNDCLASSEXW>() as _,
73 lpfnWndProc: Some(Self::base_wnd_proc),
74 hInstance: unsafe { GetModuleHandleW(PCWSTR::NULL)? }.into(),
75 lpszClassName: PCWSTR(HSTRING::from(name).as_ptr()),
76 ..Default::default()
77 },
78 wnd_proc,
79 )
80 }
81
82 pub fn with_details<F>(
83 mut wnd_class_ex: WNDCLASSEXW,
84 wnd_proc: F,
85 ) -> windows::core::Result<Self>
86 where
87 F: WndProc + 'a,
88 {
89 wnd_class_ex.lpfnWndProc = Some(Self::base_wnd_proc);
92
93 Ok(Self {
94 atom: unsafe { RegisterClassExW(&wnd_class_ex) }.nonzero_or_win32_err()?,
95 wnd_proc_ptr: Box::into_raw(Box::new(Box::new(wnd_proc))),
97 })
98 }
99
100 pub fn make_name() -> windows::core::Result<String> {
101 let mut precise_time = 0;
104 unsafe { QueryPerformanceCounter(&mut precise_time)? };
105
106 Ok(format!("unnamed_{precise_time:x}"))
107 }
108
109 pub fn atom(&self) -> u16 {
110 self.atom
111 }
112
113 extern "system" fn base_wnd_proc(
114 hwnd: HWND,
115 msg_id: u32,
116 wparam: WPARAM,
117 lparam: LPARAM,
118 ) -> LRESULT {
119 let mut user_data = unsafe {
121 SetLastError(ERROR_SUCCESS);
122 GetWindowLongPtrW(hwnd, GWLP_USERDATA)
123 };
124
125 if user_data == 0 {
127 user_data = NEXT_WINDOW_USER_DATA_ON_INIT.replace(0);
129
130 let result = Result::<(), windows::core::Error>::from_win32().and_then(|_| unsafe {
131 SetLastError(ERROR_SUCCESS);
132 SetWindowLongPtrW(hwnd, GWLP_USERDATA, user_data).nonzero_with_win32_or_err()
133 });
134
135 if result.is_err() {
136 return LRESULT(0);
139 }
140 };
141
142 let wnd_proc = unsafe { &mut *(user_data as *mut Box<dyn WndProc>) };
145
146 if let Some(lresult) = wnd_proc(hwnd, msg_id, wparam, lparam) {
147 lresult
148 } else {
149 unsafe { DefWindowProcW(hwnd, msg_id, wparam, lparam) }
151 }
152 }
153}
154
155impl Drop for WindowClass<'_> {
156 fn drop(&mut self) {
157 unsafe {
158 if let Ok(h_module) = GetModuleHandleW(PCWSTR::NULL) {
159 let result = UnregisterClassW(PCWSTR(self.atom as _), h_module);
160 debug_assert!(
161 result.is_ok(),
162 "couldn't unregister window class (did you adhere to proper drop order?): {result:?}"
163 );
164 }
165
166 drop(Box::from_raw(self.wnd_proc_ptr));
167 }
168 }
169}
170
171pub struct Window {
175 hwnd: HWND,
176}
177
178impl Window {
179 pub fn new_msg_only(class: &WindowClass) -> windows::core::Result<Self> {
180 Self::with_details(
185 class,
186 Some(HWND_MESSAGE),
187 WINDOW_STYLE(0),
188 None,
189 None,
190 None,
191 None,
192 )
193 }
194
195 pub fn new_invisible(class: &WindowClass) -> windows::core::Result<Self> {
196 Self::with_details(
199 class,
200 None,
201 WINDOW_STYLE(0),
202 None,
203 Some((POINT::zeroed(), SIZE::zeroed())),
204 None,
205 None,
206 )
207 }
208
209 pub fn with_details(
210 class: &WindowClass,
211 parent: Option<HWND>,
212 style: WINDOW_STYLE,
213 ex_style: Option<WINDOW_EX_STYLE>,
214 placement: Option<(POINT, SIZE)>,
215 text: Option<PCWSTR>,
216 menu: Option<HMENU>,
217 ) -> windows::core::Result<Self> {
218 NEXT_WINDOW_USER_DATA_ON_INIT.set(class.wnd_proc_ptr as _);
224
225 let (pos, size) = placement.unwrap_or((
227 POINT {
228 x: CW_USEDEFAULT,
229 y: CW_USEDEFAULT,
230 },
231 SIZE {
232 cx: CW_USEDEFAULT,
233 cy: CW_USEDEFAULT,
234 },
235 ));
236
237 let hwnd = unsafe {
238 CreateWindowExW(
239 ex_style.unwrap_or(WINDOW_EX_STYLE(0)),
240 PCWSTR(class.atom as _),
241 text.unwrap_or(PCWSTR::NULL),
242 style,
243 pos.x,
244 pos.y,
245 size.cx,
246 size.cy,
247 parent.unwrap_or(HWND::NULL),
248 menu.unwrap_or(HMENU::NULL),
249 GetModuleHandleW(PCWSTR::NULL)?,
250 None,
251 )
252 };
253 #[cfg(any(feature = "windows_v0_48", feature = "windows_v0_52"))]
254 let hwnd = hwnd.nonnull_or_e_handle()?; #[cfg(not(any(feature = "windows_v0_48", feature = "windows_v0_52")))]
256 let hwnd = hwnd?;
257
258 Ok(Self { hwnd })
259 }
260
261 pub fn hwnd(&self) -> HWND {
262 self.hwnd
263 }
264
265 pub fn is_valid(&self) -> bool {
266 unsafe { IsWindow(self.hwnd) }.as_bool()
273 }
274}
275
276impl Drop for Window {
277 fn drop(&mut self) {
278 let _ = unsafe { DestroyWindow(self.hwnd) };
280 }
281}
282
283#[cfg(all(test, feature = "windows_latest_compatible_all"))]
284mod tests {
285 use super::{Window, WindowClass};
286 use crate::{foundation::LParamExt, win32_app::msg_loop, windows, Null};
287 use std::{cell::RefCell, rc::Rc};
288 use windows::{
289 core::{w, HSTRING, PCWSTR},
290 Win32::{
291 Foundation::{HWND, LRESULT, POINT, SIZE},
292 UI::WindowsAndMessaging::{
293 MessageBoxW, PostQuitMessage, MB_OK, MINMAXINFO, WM_DESTROY, WM_GETMINMAXINFO,
294 WM_LBUTTONUP, WS_OVERLAPPEDWINDOW, WS_VISIBLE,
295 },
296 },
297 };
298
299 #[ignore]
300 #[test]
301 fn create_window() -> windows::core::Result<()> {
302 let counter = Rc::new(RefCell::new(1));
303
304 let class = WindowClass::new(|hwnd, msg_id, wparam, mut lparam| {
305 println!("window msg received: {hwnd:?}, msg 0x{msg_id:04x}, {wparam:?}, {lparam:?}");
306
307 match msg_id {
308 WM_LBUTTONUP => {
309 *counter.borrow_mut() += 1;
310
311 unsafe {
312 MessageBoxW(
313 HWND::NULL,
314 PCWSTR(HSTRING::from(format!("{counter:?}")).as_ptr()),
315 w!("Message Box"),
316 MB_OK,
317 )
318 };
319
320 Some(LRESULT(0))
321 }
322 WM_GETMINMAXINFO => {
323 let min_max_info = unsafe { lparam.cast_to_mut::<MINMAXINFO>() };
324 min_max_info.ptMaxTrackSize = POINT { x: 300, y: 300 };
325
326 Some(LRESULT(0))
327 }
328 WM_DESTROY => {
329 unsafe { PostQuitMessage(0) };
330 Some(LRESULT(0))
331 }
332 _ => None,
333 }
334 })?;
335
336 *counter.borrow_mut() += 1;
337
338 let _window = Window::with_details(
339 &class,
340 None,
341 WS_OVERLAPPEDWINDOW | WS_VISIBLE,
342 None,
343 Some((POINT { x: 100, y: 100 }, SIZE { cx: 500, cy: 500 })),
344 Some(PCWSTR(HSTRING::from("Test Window").as_ptr())),
345 None,
346 )?;
347
348 *counter.borrow_mut() += 1;
349
350 msg_loop::run()?;
351
352 Ok(())
353 }
354}