win_msgbox/lib.rs
1//! This crate provides a fully featured, ergonomic interface to Windows' [`MessageBox`](https://learn.microsoft.com/ewindows/win32/api/winuser/nf-winuser-messagebox).
2//!
3//! All possible options are usable and return values are Rust enums (or structs if only one option is available).
4//!
5//! All configuration is done through [MessageBox] and available buttons are configured via [Options].
6//!
7//! `message` and `title` will be converted to UTF-16 when calling [show][MessageBox::show] on the fly.
8//! If this isn't desired, use the structs and functions exported in the [raw] module. However, note that these are
9//! `unsafe`, as they assume the passed pointers point to valid, null-terminated UTF-16 strings.
10//!
11//! ## Examples
12//!
13//! Show a minimal message box with an **OK** button:
14//!
15//! ```no_run
16//! use win_msgbox::Okay;
17//! win_msgbox::show::<Okay>("Hello World");
18//! ```
19//!
20//! Show a message box with an error icon, and match on the return value:
21//!
22//! ```no_run
23//! use win_msgbox::CancelTryAgainContinue::{self, *};
24//!
25//! # fn main() -> win_msgbox::Result<()> {
26//! let response = win_msgbox::error::<CancelTryAgainContinue>("Couldn't download resource")
27//! .title("Download Error")
28//! .show()?;
29//!
30//! match response {
31//! Cancel => println!("Cancelling downlaod..."),
32//! TryAgain => println!("Attempting redownload..."),
33//! Continue => println!("Skipping resource"),
34//! }
35//! # Ok(())
36//! # }
37//! ```
38#![deny(missing_docs)]
39#![deny(clippy::cargo)]
40use std::marker::PhantomData;
41use windows_sys::Win32::{
42 Foundation::{GetLastError, HWND},
43 UI::WindowsAndMessaging::{
44 MessageBoxW, MB_APPLMODAL, MB_DEFAULT_DESKTOP_ONLY, MB_DEFBUTTON1, MB_DEFBUTTON2,
45 MB_DEFBUTTON3, MB_DEFBUTTON4, MB_HELP, MB_ICONASTERISK, MB_ICONERROR, MB_ICONEXCLAMATION,
46 MB_ICONHAND, MB_ICONINFORMATION, MB_ICONQUESTION, MB_ICONSTOP, MB_ICONWARNING, MB_RIGHT,
47 MB_RTLREADING, MB_SERVICE_NOTIFICATION, MB_SETFOREGROUND, MB_SYSTEMMODAL, MB_TASKMODAL,
48 MB_TOPMOST, MESSAGEBOX_RESULT, MESSAGEBOX_STYLE,
49 },
50};
51
52mod abort_retry_ignore;
53mod cancel_try_again_continue;
54mod okay;
55mod okay_cancel;
56pub mod raw;
57mod retry_cancel;
58mod yes_no;
59mod yes_no_cancel;
60
61pub use abort_retry_ignore::*;
62pub use cancel_try_again_continue::*;
63pub use okay::*;
64pub use okay_cancel::*;
65pub use retry_cancel::*;
66pub use yes_no::*;
67pub use yes_no_cancel::*;
68
69/// Raw error returned by [GetLastError](https://learn.microsoft.com/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror).
70pub type Error = windows_sys::Win32::Foundation::WIN32_ERROR;
71
72/// Convenience wrapper type for a `Result<T, win_msgbox::Error>`.
73pub type Result<T> = core::result::Result<T, Error>;
74
75/// This trait is implemented for all possible options.
76///
77/// Available are:
78///
79/// - [**Abort**, **Retry**, and **Ignore**](AbortRetryIgnore)
80/// - [**Cancel**, **Try Again**, and **Continue**](CancelTryAgainContinue)
81/// - [**OK**](Okay)
82/// - [**OK**, and **Cancel**](OkayCancel)
83/// - [**Retry**, and **Cancel**](RetryCancel)
84/// - [**Yes**, and **No**](YesNo)
85/// - [**Yes**, **No**, and **Cancel**](YesNoCancel)
86pub trait Options: From<MESSAGEBOX_RESULT> {
87 /// The flags this option requires to be shown.
88 fn flags() -> MESSAGEBOX_STYLE;
89}
90
91/// The icon to be displayed in a message box.
92#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)]
93#[repr(u32)] // = MESSAGEBOX_STYLE
94pub enum Icon {
95 /// An exclamation-point icon appears in the message box.
96 Exclamation,
97 /// An exclamation-point icon appears in the message box.
98 Warning,
99 /// An icon consisting of a lowercase letter i in a circle appears in the message box.
100 Information,
101 /// An icon consisting of a lowercase letter i in a circle appears in the message box.
102 Asterisk,
103 /// A question-mark icon appears in the message box.
104 /// The question-mark message icon is no longer recommended
105 /// because it does not clearly represent a specific type of message
106 /// and because the phrasing of a message as a question could apply to any message type.
107 /// In addition, users can confuse the message symbol question mark with Help information.
108 /// Therefore, do not use this question mark message symbol in your message boxes.
109 /// The system continues to support its inclusion only for backward compatibility.
110 Question,
111 /// A stop-sign icon appears in the message box.
112 Stop,
113 /// A stop-sign icon appears in the message box.
114 Error,
115 /// A stop-sign icon appears in the message box.
116 Hand,
117}
118
119impl Icon {
120 fn style(self) -> MESSAGEBOX_STYLE {
121 match self {
122 Icon::Exclamation => MB_ICONEXCLAMATION,
123 Icon::Warning => MB_ICONWARNING,
124 Icon::Information => MB_ICONINFORMATION,
125 Icon::Asterisk => MB_ICONASTERISK,
126 Icon::Question => MB_ICONQUESTION,
127 Icon::Stop => MB_ICONSTOP,
128 Icon::Error => MB_ICONERROR,
129 Icon::Hand => MB_ICONHAND,
130 }
131 }
132}
133
134/// Specifies the modality of the dialog box.
135#[derive(Debug, Default, Eq, PartialEq, Clone, Copy, Hash)]
136#[repr(u32)] // = MESSAGEBOX_STYLE
137pub enum Modal {
138 /// The user must respond to the message box before continuing work in the window identified by the [`hwnd`](MessageBox::hwnd).
139 /// However, the user can move to the windows of other threads and work in those windows.
140 /// Depending on the hierarchy of windows in the application,
141 /// the user may be able to move to other windows within the thread.
142 /// All child windows of the parent of the message box are automatically disabled,
143 /// but pop-up windows are not.
144 #[default]
145 Application = MB_APPLMODAL,
146 /// Same as [`Application`](Self::Application) except that the message box has the `WS_EX_TOPMOST` style.
147 /// Use system-modal message boxes to notify the user of serious,
148 /// potentially damaging errors that require immediate attention (for example, running out of memory).
149 /// This flag has no effect on the user's ability to interact with windows other than those associated with [`hwnd`](MessageBox::hwnd).
150 System = MB_SYSTEMMODAL,
151 /// Same as [`Application`](Self::Application) except that all the top-level windows belonging to the current thread are disabled
152 /// if the [`hwnd`](MessageBox::hwnd) parameter is `0`. Use this flag when the calling application
153 /// or library does not have a window handle available but still needs to prevent input to other windows in the calling thread
154 /// without suspending other threads.
155 Task = MB_TASKMODAL,
156}
157
158/// Specifies the default button of the dialog box.
159///
160/// The meaning of the nth button is determined by the type ([Options]).
161#[derive(Debug, Default, Eq, PartialEq, Clone, Copy, Hash)]
162#[repr(u32)] // = MESSAGEBOX_STYLE
163pub enum DefaultButton {
164 /// The first button is the default button.
165 #[default]
166 DefaultButton1 = MB_DEFBUTTON1,
167 /// The second button is the default button.
168 DefaultButton2 = MB_DEFBUTTON2,
169 /// The third button is the default button.
170 DefaultButton3 = MB_DEFBUTTON3,
171 /// The fourth button is the default button.
172 DefaultButton4 = MB_DEFBUTTON4,
173}
174
175/// A builder for a modal dialog box that contains a system icon,
176/// a set of buttons, and a brief application-specific message, such as status or error information.
177///
178/// The type of the message box is specified by `T` (See [Options] for available options).
179pub struct MessageBox<'a, T> {
180 /// The icon of this message box.
181 icon: Icon,
182 /// The text inside the message box.
183 text: &'a str,
184 /// The title of the message box (default is None).
185 title: Option<&'a str>,
186 /// The owner window of the message box (default is `0` - no owner)
187 hwnd: HWND,
188 /// Flags for the creation of this message box.
189 flags: MESSAGEBOX_STYLE,
190 /// The response options of message box.
191 _response: PhantomData<T>,
192}
193
194impl<T> std::fmt::Debug for MessageBox<'_, T> {
195 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196 f.debug_struct("MessageBox")
197 .field("title", &self.title)
198 .field("text", &self.text)
199 .field("icon", &self.icon)
200 .field("hwnd", &self.hwnd)
201 .finish()
202 }
203}
204
205macro_rules! ctors {
206 ($($name:ident => $icon:ident),*) => {
207 impl <'a, T> MessageBox<'a, T> {
208 $(
209 #[doc = concat!("Creates a new message box where its icon is set to [", stringify!($icon), "](Icon::", stringify!($icon),").")]
210 pub fn $name(text: &'a str) -> Self {
211 Self::new(text).icon(Icon::$icon)
212 }
213 )*
214 }
215 $(
216 #[doc = concat!("Creates a new message box where its icon is set to [", stringify!($icon), "](Icon::", stringify!($icon),").")]
217 pub fn $name<T>(text: &str) -> MessageBox<'_, T> {
218 MessageBox::<T>::$name(text)
219 })*
220 };
221}
222
223impl<'a, T> MessageBox<'a, T> {
224 /// Creates a new message box with a specified `text` to be displayed.
225 /// If the string consists of more than one line,
226 /// you can separate the lines using a carriage return and/or linefeed character between each line.
227 pub fn new(text: &'a str) -> Self {
228 Self {
229 icon: Icon::Information,
230 text,
231 title: None,
232 hwnd: std::ptr::null_mut(),
233 flags: 0,
234 _response: PhantomData,
235 }
236 }
237
238 /// The [Icon] to be displayed in this message box.
239 pub fn icon(mut self, icon: Icon) -> Self {
240 self.icon = icon;
241 self
242 }
243
244 /// The dialog box title. If this parameter is **null**, the default title is **Error**.
245 pub fn title(mut self, title: &'a str) -> Self {
246 self.title = title.into();
247 self
248 }
249
250 /// A handle to the owner window of the message box to be created.
251 /// If this parameter is `0`, the message box has no owner window (default).
252 pub fn hwnd(mut self, hwnd: HWND) -> Self {
253 self.hwnd = hwnd;
254 self
255 }
256
257 /// Set the modality of the dialog box. See [Modal] for options.
258 pub fn modal(mut self, modal: Modal) -> Self {
259 self.flags |= modal as u32;
260 self
261 }
262
263 /// Set the default button of the dialog box. See [DefaultButton] for options.
264 pub fn default_button(mut self, btn: DefaultButton) -> Self {
265 self.flags |= btn as u32;
266 self
267 }
268
269 /// Same as desktop of the interactive window station. For more information, see [Window Stations](https://learn.microsoft.com/windows/desktop/winstation/window-stations).
270 /// If the current input desktop is not the default desktop,
271 /// [show](Self::show) does not return until the user switches to the default desktop.
272 pub fn default_desktop_only(mut self) -> Self {
273 self.flags |= MB_DEFAULT_DESKTOP_ONLY;
274 self
275 }
276
277 /// The text is right-justified.
278 pub fn right(mut self) -> Self {
279 self.flags |= MB_RIGHT;
280 self
281 }
282
283 /// Displays message and caption text using right-to-left reading order on Hebrew and Arabic systems.
284 pub fn rtl_reading(mut self) -> Self {
285 self.flags |= MB_RTLREADING;
286 self
287 }
288
289 /// The message box becomes the foreground window.
290 /// Internally, the system calls the [SetForegroundWindow](https://learn.microsoft.com/windows/desktop/api/winuser/nf-winuser-setforegroundwindow) function for the message box.
291 pub fn set_foreground(mut self) -> Self {
292 self.flags |= MB_SETFOREGROUND;
293 self
294 }
295
296 /// The message box is created with the `WS_EX_TOPMOST` window style.
297 pub fn topmost(mut self) -> Self {
298 self.flags |= MB_TOPMOST;
299 self
300 }
301
302 /// The caller is a service notifying the user of an event.
303 /// The function displays a message box on the current active desktop,
304 /// even if there is no user logged on to the computer.
305 ///
306 /// Terminal Services: If the calling thread has an impersonation token,
307 /// the function directs the message box to the session specified in the impersonation token.
308 ///
309 /// If this is called, [`hwnd`](Self::hwnd) must not be called - it must remain `0`.
310 /// his is so that the message box can appear on a desktop other than the desktop corresponding to the `hwnd`.
311 ///
312 /// For information on security considerations in regard to using this flag, see [Interactive Services](https://learn.microsoft.com/windows/desktop/Services/interactive-services).
313 /// In particular, be aware that this flag can produce interactive content on a locked desktop
314 /// and should therefore be used for only a very limited set of scenarios, such as resource exhaustion.
315 pub fn service_notification(mut self) -> Self {
316 self.flags |= MB_SERVICE_NOTIFICATION;
317 self
318 }
319
320 /// Adds a Help button to the message box.
321 /// When the user clicks the Help button or presses F1,
322 /// the system sends a [WM_HELP](https://learn.microsoft.com/windows/desktop/shell/wm-help) message to the owner.
323 pub fn with_help(mut self) -> Self {
324 self.flags |= MB_HELP;
325 self
326 }
327}
328
329impl<T: Options> MessageBox<'_, T> {
330 /// Shows the message box, returning the option the user clicked on.
331 ///
332 /// If a message box has a **Cancel** button, the function returns the `Cancel` value
333 /// if either the ESC key is pressed or the **Cancel** button is selected.
334 ///
335 /// If the message box has no **Cancel** button, pressing ESC will no effect -
336 /// unless an **Ok** button is present.
337 ///
338 /// If an **Ok** button is displayed and the user presses ESC, the return value will be `Ok`.
339 pub fn show(self) -> Result<T> {
340 let text: Vec<_> = self.text.encode_utf16().chain(std::iter::once(0)).collect();
341 let title = match self.title {
342 Some(t) => t.encode_utf16().chain(std::iter::once(0)).collect(),
343 None => Vec::new(),
344 };
345
346 let return_code = unsafe {
347 MessageBoxW(
348 self.hwnd,
349 text.as_ptr(),
350 if title.is_empty() {
351 std::ptr::null()
352 } else {
353 title.as_ptr()
354 },
355 T::flags() | self.icon.style() | self.flags,
356 )
357 };
358 match return_code {
359 0 => Err(unsafe { GetLastError() }),
360 x => Ok(T::from(x)),
361 }
362 }
363}
364
365ctors! {
366 exclamation => Exclamation,
367 warning => Warning,
368 information => Information,
369 asterisk => Asterisk,
370 question => Question,
371 stop => Stop,
372 error => Error,
373 hand => Hand
374}
375
376/// Shows a message box with a specified `text` to be displayed.
377///
378/// For more options see [MessageBox].
379pub fn show<T: Options>(text: &str) -> Result<T> {
380 MessageBox::new(text).show()
381}