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}