win_msgbox/
raw.rs

1//! This module provides the raw functionality to avoid having to convert UTF-8 to UTF-16 when showing a message box.
2//! Otherwise, it's identical to the main exports.
3//!
4//! ## Safety
5//!
6//! Note that these functions are `unsafe`, as they assume the strings passed as `message` and `title`
7//! are and will remain valid UTF-16 and null terminated until [show][MessageBox::show] is called.
8//! This is especially important when passing strings created at runtime.
9//!
10//! To create a wide string statically, use the `w!` macro re-exported by this module from
11//! [`windows_sys`](https://docs.rs/windows-sys/latest/windows_sys/macro.w.html).
12//!
13//! ## Examples
14//!
15//! Show a minimal message box with an **OK** button:
16//!
17//! ```no_run
18//! use win_msgbox::{raw::{show, w}, Okay};
19//! unsafe { show::<Okay>(w!("Hello World")); }
20//! ```
21//!
22//! Show a message box with an error icon, and match on the return value:
23//!
24//! ```no_run
25//! use win_msgbox::{raw::{error, w}, CancelTryAgainContinue::{self, *}};
26//!
27//! # fn main() -> win_msgbox::Result<()> {
28//! // Safety: `w!` creates null-terminated UTF-16 strings at compile time,
29//! //         thus the pointed-to value never changes.
30//! let response = unsafe {
31//!     error::<CancelTryAgainContinue>(w!("Couldn't download resource"))
32//!         .title(w!("Download Error"))
33//!         .show()?
34//! };
35//!
36//! match response {
37//!     Cancel => println!("Cancelling downlaod..."),
38//!     TryAgain => println!("Attempting redownload..."),
39//!     Continue => println!("Skipping resource"),
40//! }
41//! #    Ok(())
42//! # }
43//! ```
44#![deny(missing_docs)]
45#![deny(clippy::cargo)]
46use std::marker::PhantomData;
47use windows_sys::{
48    core::PCWSTR,
49    Win32::{
50        Foundation::{GetLastError, HWND},
51        UI::WindowsAndMessaging::{
52            MessageBoxW, MB_DEFAULT_DESKTOP_ONLY, MB_HELP, MB_RIGHT, MB_RTLREADING,
53            MB_SERVICE_NOTIFICATION, MB_SETFOREGROUND, MB_TOPMOST, MESSAGEBOX_STYLE,
54        },
55    },
56};
57
58use crate::{DefaultButton, Icon, Modal, Options, Result};
59
60pub use windows_sys::w;
61
62/// A builder for a modal dialog box that contains a system icon,
63/// a set of buttons, and a brief application-specific message, such as status or error information.
64///
65/// The type of the message box is specified by `T` (See [Options] for available options).
66pub struct MessageBox<T> {
67    /// The icon of this message box.
68    icon: Icon,
69    /// The text inside the message box.
70    text: PCWSTR,
71    /// The title of the message box (default is null).
72    title: PCWSTR,
73    /// The owner window of the message box (default is `0` - no owner)
74    hwnd: HWND,
75    /// Flags for the creation of this message box.
76    flags: MESSAGEBOX_STYLE,
77    /// The response options of message box.
78    _response: PhantomData<T>,
79}
80
81impl<T> std::fmt::Debug for MessageBox<T> {
82    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83        f.debug_struct("MessageBox")
84            .field("icon", &self.icon)
85            .field("hwnd", &self.hwnd)
86            .finish()
87    }
88}
89
90macro_rules! ctors {
91    ($($name:ident => $icon:ident),*) => {
92        impl <T> MessageBox<T> {
93            $(
94            #[doc = concat!("Creates a new message box where its icon is set to [", stringify!($icon), "](Icon::", stringify!($icon),").")]
95            pub fn $name(text: impl Into<PCWSTR>) -> Self {
96                Self::new(text).icon(Icon::$icon)
97            }
98            )*
99        }
100        $(
101        #[doc = concat!("Creates a new message box where its icon is set to [", stringify!($icon), "](Icon::", stringify!($icon),").")]
102        pub fn $name<T>(text: impl Into<PCWSTR>) -> MessageBox<T> {
103            MessageBox::<T>::$name(text)
104        })*
105    };
106}
107
108impl<T> MessageBox<T> {
109    /// Creates a new message box with a specified `text` to be displayed.
110    /// If the string consists of more than one line,
111    /// you can separate the lines using a carriage return and/or linefeed character between each line.
112    pub fn new(text: impl Into<PCWSTR>) -> Self {
113        Self {
114            icon: Icon::Information,
115            text: text.into(),
116            title: std::ptr::null(),
117            hwnd: std::ptr::null_mut(),
118            flags: 0,
119            _response: PhantomData,
120        }
121    }
122
123    /// The [Icon] to be displayed in this message box.
124    pub fn icon(mut self, icon: Icon) -> Self {
125        self.icon = icon;
126        self
127    }
128
129    /// The dialog box title. If this parameter is **null**, the default title is **Error**.
130    pub fn title(mut self, title: impl Into<PCWSTR>) -> Self {
131        self.title = title.into();
132        self
133    }
134
135    /// A handle to the owner window of the message box to be created.
136    /// If this parameter is `0`, the message box has no owner window (default).
137    pub fn hwnd(mut self, hwnd: HWND) -> Self {
138        self.hwnd = hwnd;
139        self
140    }
141
142    /// Set the modality of the dialog box. See [Modal] for options.
143    pub fn modal(mut self, modal: Modal) -> Self {
144        self.flags |= modal as u32;
145        self
146    }
147
148    /// Set the default button of the dialog box. See [DefaultButton] for options.
149    pub fn default_button(mut self, btn: DefaultButton) -> Self {
150        self.flags |= btn as u32;
151        self
152    }
153
154    /// Same as desktop of the interactive window station. For more information, see [Window Stations](https://learn.microsoft.com/windows/desktop/winstation/window-stations).
155    /// If the current input desktop is not the default desktop,
156    /// [show](Self::show) does not return until the user switches to the default desktop.
157    pub fn default_desktop_only(mut self) -> Self {
158        self.flags |= MB_DEFAULT_DESKTOP_ONLY;
159        self
160    }
161
162    /// The text is right-justified.
163    pub fn right(mut self) -> Self {
164        self.flags |= MB_RIGHT;
165        self
166    }
167
168    /// Displays message and caption text using right-to-left reading order on Hebrew and Arabic systems.
169    pub fn rtl_reading(mut self) -> Self {
170        self.flags |= MB_RTLREADING;
171        self
172    }
173
174    /// The message box becomes the foreground window.
175    /// Internally, the system calls the [SetForegroundWindow](https://learn.microsoft.com/windows/desktop/api/winuser/nf-winuser-setforegroundwindow) function for the message box.
176    pub fn set_foreground(mut self) -> Self {
177        self.flags |= MB_SETFOREGROUND;
178        self
179    }
180
181    /// The message box is created with the `WS_EX_TOPMOST` window style.
182    pub fn topmost(mut self) -> Self {
183        self.flags |= MB_TOPMOST;
184        self
185    }
186
187    /// The caller is a service notifying the user of an event.
188    /// The function displays a message box on the current active desktop,
189    /// even if there is no user logged on to the computer.
190    ///
191    /// Terminal Services: If the calling thread has an impersonation token,
192    /// the function directs the message box to the session specified in the impersonation token.
193    ///
194    /// If this is called, [`hwnd`](Self::hwnd) must not be called - it must remain `0`.
195    /// his is so that the message box can appear on a desktop other than the desktop corresponding to the `hwnd`.
196    ///
197    /// For information on security considerations in regard to using this flag, see [Interactive Services](https://learn.microsoft.com/windows/desktop/Services/interactive-services).
198    /// In particular, be aware that this flag can produce interactive content on a locked desktop
199    /// and should therefore be used for only a very limited set of scenarios, such as resource exhaustion.
200    pub fn service_notification(mut self) -> Self {
201        self.flags |= MB_SERVICE_NOTIFICATION;
202        self
203    }
204
205    /// Adds a Help button to the message box.
206    /// When the user clicks the Help button or presses F1,
207    /// the system sends a [WM_HELP](https://learn.microsoft.com/windows/desktop/shell/wm-help) message to the owner.
208    pub fn with_help(mut self) -> Self {
209        self.flags |= MB_HELP;
210        self
211    }
212}
213
214impl<T: Options> MessageBox<T> {
215    /// Shows the message box, returning the option the user clicked on.
216    ///
217    /// If a message box has a **Cancel** button, the function returns the `Cancel` value
218    /// if either the ESC key is pressed or the **Cancel** button is selected.
219    ///
220    /// If the message box has no **Cancel** button, pressing ESC will no effect -
221    /// unless an **Ok** button is present.
222    ///
223    /// If an **Ok** button is displayed and the user presses ESC, the return value will be `Ok`.
224    ///
225    /// ### Safety
226    ///
227    /// [`text`][Self::new] and [`title`][Self::title] (if set) must point to a valid 16 bit, null terminated string.
228    pub unsafe fn show(self) -> Result<T> {
229        let return_code = MessageBoxW(
230            self.hwnd,
231            self.text,
232            self.title,
233            T::flags() | self.icon.style() | self.flags,
234        );
235        match return_code {
236            0 => Err(GetLastError()),
237            x => Ok(T::from(x)),
238        }
239    }
240}
241
242ctors! {
243    exclamation => Exclamation,
244    warning => Warning,
245    information => Information,
246    asterisk => Asterisk,
247    question => Question,
248    stop => Stop,
249    error => Error,
250    hand => Hand
251}
252
253/// Shows a message box with a specified `text` to be displayed.
254///
255/// For more options see [MessageBox].
256///
257/// ### Safety
258///
259/// `text` must point to a valid 16 bit, null terminated string.
260pub unsafe fn show<T: Options>(text: impl Into<PCWSTR>) -> Result<T> {
261    MessageBox::new(text).show()
262}