ergonomic-windows 0.1.0

Ergonomic, safe Rust wrappers for Windows APIs - handles, processes, registry, file system, UI controls, Direct2D graphics, and more
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
//! Window creation and message handling utilities.
//!
//! Provides ergonomic wrappers for creating windows and handling Windows messages.

use crate::error::Result;
use crate::string::WideString;
use std::cell::RefCell;
use windows::Win32::Foundation::{HWND, LPARAM, LRESULT, WPARAM};
use windows::Win32::Graphics::Gdi::{GetStockObject, HBRUSH, WHITE_BRUSH};
use windows::Win32::System::LibraryLoader::GetModuleHandleW;
use windows::Win32::UI::WindowsAndMessaging::{
    CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, GetMessageW,
    GetWindowLongPtrW, LoadCursorW, PostQuitMessage, RegisterClassExW, SetWindowLongPtrW,
    ShowWindow, TranslateMessage, UnregisterClassW, CS_HREDRAW, CS_VREDRAW, CW_USEDEFAULT,
    GWLP_USERDATA, IDC_ARROW, MSG, SW_HIDE, SW_SHOW, SW_SHOWDEFAULT, WINDOW_EX_STYLE, WINDOW_STYLE,
    WM_CLOSE, WM_CREATE, WM_DESTROY, WM_NCCREATE, WNDCLASSEXW, WS_CAPTION, WS_OVERLAPPEDWINDOW,
    WS_SYSMENU, WS_VISIBLE,
};

/// Window styles for creating windows.
#[derive(Clone, Copy, Debug)]
pub struct Style(pub WINDOW_STYLE);

impl Style {
    /// A standard overlapped window with title bar, border, and system menu.
    pub const OVERLAPPED: Self = Self(WS_OVERLAPPEDWINDOW);

    /// A window with a caption.
    pub const CAPTION: Self = Self(WS_CAPTION);

    /// A window with a system menu.
    pub const SYSMENU: Self = Self(WS_SYSMENU);

    /// A visible window.
    pub const VISIBLE: Self = Self(WS_VISIBLE);

    /// Combines two styles.
    pub fn with(self, other: Self) -> Self {
        Self(WINDOW_STYLE(self.0 .0 | other.0 .0))
    }
}

/// Extended window styles.
#[derive(Clone, Copy, Debug, Default)]
pub struct ExStyle(pub WINDOW_EX_STYLE);

impl ExStyle {
    /// No extended styles.
    pub const NONE: Self = Self(WINDOW_EX_STYLE(0));

    /// Combines two extended styles.
    pub fn with(self, other: Self) -> Self {
        Self(WINDOW_EX_STYLE(self.0 .0 | other.0 .0))
    }
}

/// Show window commands.
#[derive(Clone, Copy, Debug)]
pub struct ShowCommand(pub windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD);

impl ShowCommand {
    /// Shows the window in its default state.
    pub const DEFAULT: Self = Self(SW_SHOWDEFAULT);

    /// Shows the window normally.
    pub const SHOW: Self = Self(SW_SHOW);

    /// Hides the window.
    pub const HIDE: Self = Self(SW_HIDE);
}

/// A Windows message.
#[derive(Clone, Copy, Debug)]
pub struct Message {
    /// The window handle.
    pub hwnd: HWND,
    /// The message identifier.
    pub msg: u32,
    /// Additional message information.
    pub wparam: WPARAM,
    /// Additional message information.
    pub lparam: LPARAM,
}

impl Message {
    /// WM_CREATE message.
    pub const CREATE: u32 = WM_CREATE;
    /// WM_DESTROY message.
    pub const DESTROY: u32 = WM_DESTROY;
    /// WM_CLOSE message.
    pub const CLOSE: u32 = WM_CLOSE;
}

/// Trait for handling window messages.
pub trait MessageHandler {
    /// Handles a window message.
    ///
    /// Return `Some(result)` to indicate the message was handled, or `None` to
    /// let the default window procedure handle it.
    fn handle_message(&mut self, msg: Message) -> Option<LRESULT>;

    /// Called when the window is created.
    fn on_create(&mut self, _hwnd: HWND) -> bool {
        true
    }

    /// Called when the window is about to be destroyed.
    fn on_destroy(&mut self) {}

    /// Called when the window receives a close request.
    fn on_close(&mut self, hwnd: HWND) -> bool {
        unsafe {
            let _ = DestroyWindow(hwnd);
        }
        true
    }
}

/// A default message handler that does nothing.
pub struct DefaultHandler;

impl MessageHandler for DefaultHandler {
    fn handle_message(&mut self, _msg: Message) -> Option<LRESULT> {
        None
    }
}

/// Builder for creating windows.
pub struct WindowBuilder {
    class_name: String,
    title: String,
    style: Style,
    ex_style: ExStyle,
    x: i32,
    y: i32,
    width: i32,
    height: i32,
}

impl Default for WindowBuilder {
    fn default() -> Self {
        Self::new()
    }
}

impl WindowBuilder {
    /// Creates a new window builder with default settings.
    pub fn new() -> Self {
        Self {
            class_name: String::new(),
            title: String::from("Window"),
            style: Style::OVERLAPPED,
            ex_style: ExStyle::NONE,
            x: CW_USEDEFAULT,
            y: CW_USEDEFAULT,
            width: CW_USEDEFAULT,
            height: CW_USEDEFAULT,
        }
    }

    /// Sets the window class name.
    pub fn class_name(mut self, name: impl Into<String>) -> Self {
        self.class_name = name.into();
        self
    }

    /// Sets the window title.
    pub fn title(mut self, title: impl Into<String>) -> Self {
        self.title = title.into();
        self
    }

    /// Sets the window style.
    pub fn style(mut self, style: Style) -> Self {
        self.style = style;
        self
    }

    /// Sets the extended window style.
    pub fn ex_style(mut self, ex_style: ExStyle) -> Self {
        self.ex_style = ex_style;
        self
    }

    /// Sets the window position.
    pub fn position(mut self, x: i32, y: i32) -> Self {
        self.x = x;
        self.y = y;
        self
    }

    /// Sets the window size.
    pub fn size(mut self, width: i32, height: i32) -> Self {
        self.width = width;
        self.height = height;
        self
    }

    /// Creates the window.
    ///
    /// # Errors
    ///
    /// Returns an error if window class registration or window creation fails.
    pub fn build<H: MessageHandler + 'static>(self, handler: H) -> Result<Window<H>> {
        let class_name = if self.class_name.is_empty() {
            format!("ErgonomicWindow_{}", std::process::id())
        } else {
            self.class_name
        };

        // Register the window class
        let class_name_wide = WideString::new(&class_name);
        // SAFETY: GetModuleHandleW(None) returns the handle of the current executable.
        // It always succeeds and doesn't need to be freed.
        let hinstance = unsafe { GetModuleHandleW(None)? };

        let wc = WNDCLASSEXW {
            cbSize: std::mem::size_of::<WNDCLASSEXW>() as u32,
            style: CS_HREDRAW | CS_VREDRAW,
            lpfnWndProc: Some(window_proc::<H>),
            hInstance: hinstance.into(),
            // SAFETY: LoadCursorW with None and IDC_ARROW loads a system cursor.
            hCursor: unsafe { LoadCursorW(None, IDC_ARROW)? },
            // SAFETY: GetStockObject(WHITE_BRUSH) returns a system brush handle.
            hbrBackground: unsafe { HBRUSH(GetStockObject(WHITE_BRUSH).0) },
            lpszClassName: class_name_wide.as_pcwstr(),
            ..Default::default()
        };

        // SAFETY: wc is a properly initialized WNDCLASSEXW struct.
        let atom = unsafe { RegisterClassExW(&wc) };
        if atom == 0 {
            return Err(crate::error::last_error());
        }

        // Box the handler and convert to raw pointer.
        // We'll reconstruct and drop this in Window::drop.
        let handler = Box::new(RefCell::new(handler));
        let handler_ptr = Box::into_raw(handler);

        // Create the window
        let title_wide = WideString::new(&self.title);
        // SAFETY: All string parameters are valid null-terminated wide strings.
        // handler_ptr is passed via lpParam and will be stored in GWLP_USERDATA during WM_NCCREATE.
        let hwnd = unsafe {
            CreateWindowExW(
                self.ex_style.0,
                class_name_wide.as_pcwstr(),
                title_wide.as_pcwstr(),
                self.style.0,
                self.x,
                self.y,
                self.width,
                self.height,
                None,
                None,
                hinstance,
                Some(handler_ptr as *const _),
            )?
        };

        Ok(Window {
            hwnd,
            class_name: class_name_wide,
            handler: handler_ptr,
            hinstance,
        })
    }
}

/// A Windows window.
pub struct Window<H: MessageHandler> {
    hwnd: HWND,
    class_name: WideString,
    handler: *mut RefCell<H>,
    hinstance: windows::Win32::Foundation::HMODULE,
}

impl<H: MessageHandler> Window<H> {
    /// Returns the window handle.
    #[inline]
    pub fn hwnd(&self) -> HWND {
        self.hwnd
    }

    /// Shows the window.
    pub fn show(&self, cmd: ShowCommand) {
        // SAFETY: self.hwnd is a valid window handle created by CreateWindowExW.
        // ShowWindow is safe to call on any valid window handle.
        unsafe {
            let _ = ShowWindow(self.hwnd, cmd.0);
        }
    }

    /// Gets a mutable reference to the message handler.
    ///
    /// # Panics
    ///
    /// Panics if the handler is already borrowed (e.g., during message handling).
    pub fn handler_mut(&self) -> std::cell::RefMut<'_, H> {
        // SAFETY: self.handler is a valid pointer to a Box<RefCell<H>> that we created
        // in WindowBuilder::build. The pointer remains valid until Window is dropped.
        // RefCell provides runtime borrow checking.
        unsafe { (*self.handler).borrow_mut() }
    }

    /// Gets a reference to the message handler.
    ///
    /// # Panics
    ///
    /// Panics if the handler is already mutably borrowed (e.g., during message handling).
    pub fn handler(&self) -> std::cell::Ref<'_, H> {
        // SAFETY: self.handler is a valid pointer to a Box<RefCell<H>> that we created
        // in WindowBuilder::build. The pointer remains valid until Window is dropped.
        // RefCell provides runtime borrow checking.
        unsafe { (*self.handler).borrow() }
    }

    /// Destroys the window.
    ///
    /// This is equivalent to dropping the window.
    pub fn destroy(self) {
        // Drop will handle cleanup
    }
}

impl<H: MessageHandler> Drop for Window<H> {
    fn drop(&mut self) {
        // SAFETY: We're being dropped, so we have exclusive ownership.
        // - self.hwnd is a valid window handle we created
        // - self.class_name contains the class we registered
        // - self.handler is a valid Box pointer we created via Box::into_raw
        unsafe {
            let _ = DestroyWindow(self.hwnd);
            let _ = UnregisterClassW(self.class_name.as_pcwstr(), self.hinstance);
            // Reconstruct the Box and drop it to free the memory
            drop(Box::from_raw(self.handler));
        }
    }
}

/// The window procedure that forwards messages to the handler.
///
/// # Safety
///
/// This function is called by Windows as a callback. It must be marked `unsafe extern "system"`
/// to match the Windows calling convention. The safety of this function relies on:
/// - Windows calling it with valid parameters for the registered window class
/// - The handler pointer stored in GWLP_USERDATA being valid (set in WM_NCCREATE)
/// - The handler not being dropped while messages are being processed
unsafe extern "system" fn window_proc<H: MessageHandler>(
    hwnd: HWND,
    msg: u32,
    wparam: WPARAM,
    lparam: LPARAM,
) -> LRESULT {
    // Get the handler from the window's user data
    let handler_ptr = GetWindowLongPtrW(hwnd, GWLP_USERDATA) as *mut RefCell<H>;

    // Handle WM_NCCREATE to set up the handler pointer.
    // This is the first message sent to a window, before WM_CREATE.
    if msg == WM_NCCREATE {
        // SAFETY: During WM_NCCREATE, lparam points to a CREATESTRUCTW.
        // lpCreateParams contains the pointer we passed to CreateWindowExW.
        let create_struct =
            &*(lparam.0 as *const windows::Win32::UI::WindowsAndMessaging::CREATESTRUCTW);
        let handler_ptr = create_struct.lpCreateParams as *mut RefCell<H>;
        SetWindowLongPtrW(hwnd, GWLP_USERDATA, handler_ptr as isize);
        return DefWindowProcW(hwnd, msg, wparam, lparam);
    }

    // If we don't have a handler yet (shouldn't happen after WM_NCCREATE), use default handling.
    if handler_ptr.is_null() {
        return DefWindowProcW(hwnd, msg, wparam, lparam);
    }

    // SAFETY: handler_ptr was set in WM_NCCREATE from a valid Box<RefCell<H>>.
    // The Window struct ensures the handler outlives the window.
    let handler = &*handler_ptr;
    let message = Message {
        hwnd,
        msg,
        wparam,
        lparam,
    };

    // Handle special messages
    match msg {
        WM_CREATE => {
            let mut handler = handler.borrow_mut();
            if handler.on_create(hwnd) {
                LRESULT(0)
            } else {
                LRESULT(-1)
            }
        }
        WM_DESTROY => {
            handler.borrow_mut().on_destroy();
            PostQuitMessage(0);
            LRESULT(0)
        }
        WM_CLOSE => {
            let mut handler = handler.borrow_mut();
            let _ = handler.on_close(hwnd);
            LRESULT(0)
        }
        _ => {
            let mut handler = handler.borrow_mut();
            if let Some(result) = handler.handle_message(message) {
                result
            } else {
                DefWindowProcW(hwnd, msg, wparam, lparam)
            }
        }
    }
}

/// Runs the message loop until WM_QUIT is received.
///
/// This function blocks until the application receives a WM_QUIT message,
/// typically sent by calling `PostQuitMessage`.
///
/// # Returns
///
/// The exit code passed to `PostQuitMessage`.
pub fn run_message_loop() -> i32 {
    let mut msg = MSG::default();

    // SAFETY: GetMessageW, TranslateMessage, and DispatchMessageW are safe to call.
    // - msg is a valid stack-allocated MSG struct
    // - None for hwnd means we get messages for all windows on this thread
    // - 0, 0 for filter range means we get all message types
    unsafe {
        while GetMessageW(&mut msg, None, 0, 0).as_bool() {
            let _ = TranslateMessage(&msg);
            DispatchMessageW(&msg);
        }
    }

    msg.wParam.0 as i32
}

/// Processes pending messages without blocking.
///
/// Call this in a loop if you need to do other work while processing messages.
///
/// # Returns
///
/// Returns `true` if a WM_QUIT message was received, indicating the application should exit.
pub fn process_messages() -> bool {
    use windows::Win32::UI::WindowsAndMessaging::{PeekMessageW, PM_REMOVE, WM_QUIT};

    let mut msg = MSG::default();

    // SAFETY: PeekMessageW, TranslateMessage, and DispatchMessageW are safe to call.
    // - msg is a valid stack-allocated MSG struct
    // - PM_REMOVE removes messages from the queue after reading
    unsafe {
        while PeekMessageW(&mut msg, None, 0, 0, PM_REMOVE).as_bool() {
            if msg.message == WM_QUIT {
                return true;
            }
            let _ = TranslateMessage(&msg);
            DispatchMessageW(&msg);
        }
    }

    false
}