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