winapi_easy/
messaging.rs

1//! Messaging and message loops.
2
3use std::cell::Cell;
4use windows::Win32::Foundation::BOOL;
5use windows::Win32::UI::WindowsAndMessaging::{
6    DispatchMessageW,
7    GetMessageW,
8    PostQuitMessage,
9    TranslateMessage,
10    MSG,
11    WM_QUIT,
12};
13
14use std::io;
15
16use crate::internal::ReturnValue;
17
18/// Windows thread message loop functions.
19///
20/// This type is not meant to be instantiated.
21pub enum ThreadMessageLoop {}
22
23impl ThreadMessageLoop {
24    thread_local! {
25        static RUNNING: Cell<bool> = const { Cell::new(false) };
26        pub(crate) static ENABLE_CALLBACK_ONCE: Cell<bool> = const { Cell::new(false) };
27    }
28
29    /// Runs the Windows thread message loop.
30    ///
31    /// The user defined callback that will only be called after every user handled message.
32    /// This allows using local variables and `Result` propagation.
33    ///
34    /// Only a single message loop may be running per thread.
35    ///
36    /// # Panics
37    ///
38    /// Will panic if the message loop is already running.
39    pub fn run_thread_message_loop<F>(mut loop_callback: F) -> io::Result<()>
40    where
41        F: FnMut() -> io::Result<()>,
42    {
43        Self::RUNNING.with(|running| {
44            if running.get() {
45                panic!("Cannot run two thread message loops on the same thread");
46            }
47            running.set(true);
48        });
49        let mut msg: MSG = Default::default();
50        loop {
51            unsafe {
52                GetMessageW(&mut msg, None, 0, 0)
53                    .if_eq_to_error(BOOL(-1), io::Error::last_os_error)?;
54            }
55            if msg.message == WM_QUIT {
56                Self::RUNNING.with(|running| running.set(false));
57                break;
58            }
59            unsafe {
60                let _ = TranslateMessage(&msg);
61                DispatchMessageW(&msg);
62            }
63            if Self::ENABLE_CALLBACK_ONCE.with(|x| x.take()) {
64                loop_callback()?;
65            }
66        }
67        Ok(())
68    }
69
70    /// Posts a 'quit' message in the thread message loop.
71    ///
72    /// This will cause [`Self::run_thread_message_loop`] to return.
73    ///
74    /// # Panics
75    ///
76    /// Will panic if the message loop is not running.
77    pub fn post_quit_message() {
78        if !ThreadMessageLoop::is_loop_running() {
79            panic!("Cannot post quit message because thread message loop is not running");
80        }
81        unsafe {
82            PostQuitMessage(0);
83        }
84    }
85
86    #[inline(always)]
87    fn is_loop_running() -> bool {
88        Self::RUNNING.with(|running| running.get())
89    }
90}