1use 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
55pub struct ThreadMessageLoop(());
57
58impl ThreadMessageLoop {
59 thread_local! {
60 static RUNNING: Cell<bool> = const { Cell::new(false) };
61 }
62
63 #[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 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 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 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}