winapi_easy/
messaging.rs

1//! Messaging and message loops.
2
3use std::cell::Cell;
4use std::io;
5
6use windows::Win32::UI::WindowsAndMessaging::{
7    DispatchMessageW,
8    GetMessageW,
9    MSG,
10    PostQuitMessage,
11    TranslateMessage,
12    WM_QUIT,
13};
14use windows::core::BOOL;
15
16#[cfg(feature = "input")]
17pub use crate::input::hotkey::HotkeyId;
18use crate::internal::ReturnValue;
19#[cfg(feature = "ui")]
20pub use crate::ui::messaging::ListenerMessage;
21
22pub type RawThreadMessage = MSG;
23
24#[derive(Clone, Debug)]
25#[non_exhaustive]
26pub enum ThreadMessage {
27    #[cfg(feature = "ui")]
28    WindowProc(ListenerMessage),
29    #[cfg(feature = "input")]
30    Hotkey(u8),
31    Other(RawThreadMessage),
32}
33
34impl From<RawThreadMessage> for ThreadMessage {
35    fn from(raw_message: RawThreadMessage) -> Self {
36        match raw_message.message {
37            #[cfg(feature = "ui")]
38            crate::ui::messaging::RawMessage::ID_WINDOW_PROC_MSG => {
39                let listener_message = unsafe {
40                    Box::from_raw(std::ptr::with_exposed_provenance_mut::<ListenerMessage>(
41                        raw_message.wParam.0,
42                    ))
43                };
44                Self::WindowProc(*listener_message)
45            }
46            #[cfg(feature = "input")]
47            windows::Win32::UI::WindowsAndMessaging::WM_HOTKEY => Self::Hotkey(
48                HotkeyId::try_from(raw_message.wParam.0).expect("Hotkey ID outside of valid range"),
49            ),
50            _ => Self::Other(raw_message),
51        }
52    }
53}
54
55/// Windows thread message loop context.
56pub struct ThreadMessageLoop(());
57
58impl ThreadMessageLoop {
59    thread_local! {
60        static RUNNING: Cell<bool> = const { Cell::new(false) };
61    }
62
63    /// Creates a new thread message context.
64    ///
65    /// # Panics
66    ///
67    /// Will panic if a thread message context already exists for the current thread.
68    #[expect(clippy::new_without_default)]
69    pub fn new() -> Self {
70        assert!(
71            !Self::RUNNING.get(),
72            "Multiple message loop contexts per thread are not allowed"
73        );
74        Self::RUNNING.set(true);
75        Self(())
76    }
77
78    pub fn run(&mut self) -> io::Result<()> {
79        self.run_with(|_| Ok(()))
80    }
81
82    /// Runs the Windows thread message loop.
83    ///
84    /// The user defined callback will be called on every message except `WM_QUIT`.
85    pub fn run_with<E, F>(&mut self, loop_callback: F) -> Result<(), E>
86    where
87        E: From<io::Error>,
88        F: FnMut(ThreadMessage) -> Result<(), E>,
89    {
90        self.run_thread_message_loop_internal(loop_callback, true, None)?;
91        Ok(())
92    }
93
94    pub(crate) fn run_thread_message_loop_internal<E, F>(
95        &mut self,
96        mut loop_msg_callback: F,
97        dispatch_to_wnd_proc: bool,
98        filter_message_id: Option<u32>,
99    ) -> Result<(), E>
100    where
101        E: From<io::Error>,
102        F: FnMut(ThreadMessage) -> Result<(), E>,
103    {
104        loop {
105            match Self::process_single_thread_message(
106                self,
107                dispatch_to_wnd_proc,
108                filter_message_id,
109            )? {
110                ThreadMessageProcessingResult::Success(msg) => {
111                    loop_msg_callback(ThreadMessage::from(msg))?;
112                }
113                ThreadMessageProcessingResult::Quit => {
114                    break Ok(());
115                }
116            }
117        }
118    }
119
120    #[expect(clippy::unused_self)]
121    pub(crate) fn process_single_thread_message(
122        &mut self,
123        dispatch_to_wnd_proc: bool,
124        filter_message_id: Option<u32>,
125    ) -> io::Result<ThreadMessageProcessingResult> {
126        // Warning: Message filtering will also filter out `WM_QUIT` messages if posted via `PostThreadMessageW`.
127        let filter_message_id = filter_message_id.unwrap_or(0);
128        let mut msg: MSG = Default::default();
129        unsafe {
130            GetMessageW(&raw mut msg, None, filter_message_id, filter_message_id)
131                .if_eq_to_error(BOOL(-1), io::Error::last_os_error)?;
132        }
133        if msg.message == WM_QUIT {
134            return Ok(ThreadMessageProcessingResult::Quit);
135        }
136        if dispatch_to_wnd_proc {
137            unsafe {
138                let _ = TranslateMessage(&raw const msg);
139                DispatchMessageW(&raw const msg);
140            }
141        }
142        Ok(ThreadMessageProcessingResult::Success(msg))
143    }
144
145    /// Posts a 'quit' message in the thread message loop.
146    ///
147    /// This will cause the running message loop to return.
148    pub fn post_quit_message() {
149        unsafe {
150            PostQuitMessage(0);
151        }
152    }
153
154    #[cfg(feature = "process")]
155    pub fn post_thread_quit_message(thread_id: crate::process::ThreadId) -> io::Result<()> {
156        thread_id.post_quit_message()
157    }
158}
159
160impl Drop for ThreadMessageLoop {
161    fn drop(&mut self) {
162        Self::RUNNING.set(false);
163    }
164}
165
166#[must_use]
167pub(crate) enum ThreadMessageProcessingResult {
168    Success(MSG),
169    Quit,
170}