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}