use std::{
ptr,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
thread,
time::{Duration, SystemTime, UNIX_EPOCH},
};
use crate::{
util::{PROCESS_NAME, normalize_text, to_wide},
win32::{
FindWindowW, MB_SETFOREGROUND, MB_SYSTEMMODAL, MessageBoxExW, PostMessageW, UINT, WM_CLOSE,
},
};
#[allow(dead_code)]
enum MsgBtnType {
Ok,
OkCancel,
YesNo,
}
impl MsgBtnType {
fn to_u32(&self) -> UINT {
match self {
MsgBtnType::Ok => 0x0000,
MsgBtnType::OkCancel => 0x0001,
MsgBtnType::YesNo => 0x0004,
}
}
}
#[allow(dead_code)]
enum MsgBoxType {
Error,
Info,
Quest,
Warn,
}
impl MsgBoxType {
fn to_u32(&self) -> UINT {
match self {
MsgBoxType::Error => 0x0010,
MsgBoxType::Quest => 0x0020,
MsgBoxType::Warn => 0x0030,
MsgBoxType::Info => 0x0040,
}
}
}
impl std::fmt::Display for MsgBoxType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
MsgBoxType::Error => "Error",
MsgBoxType::Quest => "Question",
MsgBoxType::Warn => "Warning",
MsgBoxType::Info => "Info",
};
write!(f, "{}", s)
}
}
fn spawn_timeout_closer(title: Vec<u16>, timeout_ms: u64, timed_out: Arc<AtomicBool>) {
if timeout_ms == 0 {
return;
}
thread::spawn(move || {
thread::sleep(Duration::from_millis(timeout_ms));
unsafe {
let hwnd = FindWindowW(ptr::null(), title.as_ptr());
if hwnd != 0 {
timed_out.store(true, Ordering::SeqCst);
PostMessageW(hwnd, WM_CLOSE, 0, 0);
}
}
});
}
fn raw_msgbox(
msg: impl ToString,
title: impl ToString,
msgtype: MsgBoxType,
btntype: MsgBtnType,
timeout_ms: u64,
) -> i32 {
let msg = normalize_text(msg);
let title = {
let t = normalize_text(title);
let original = if t.is_empty() { msgtype.to_string() } else { t };
format!(
"{} [{}] {}",
original,
PROCESS_NAME,
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos()
)
};
let text_w = to_wide(&msg);
let title_w = to_wide(&title);
let timed_out = Arc::new(AtomicBool::new(false));
spawn_timeout_closer(title_w.clone(), timeout_ms, timed_out.clone());
let flags = btntype.to_u32() | msgtype.to_u32() | MB_SETFOREGROUND | MB_SYSTEMMODAL;
let result = unsafe { MessageBoxExW(0, text_w.as_ptr(), title_w.as_ptr(), flags, 0) };
if timed_out.load(Ordering::SeqCst) {
-1
} else {
result
}
}
#[allow(dead_code)]
pub fn info_msgbox(msg: impl ToString, title: impl ToString, timeout_ms: u64) -> i32 {
raw_msgbox(msg, title, MsgBoxType::Info, MsgBtnType::Ok, timeout_ms)
}
#[allow(dead_code)]
pub fn error_msgbox(msg: impl ToString, title: impl ToString, timeout_ms: u64) -> i32 {
raw_msgbox(msg, title, MsgBoxType::Error, MsgBtnType::Ok, timeout_ms)
}
#[allow(dead_code)]
pub fn warn_msgbox(msg: impl ToString, title: impl ToString, timeout_ms: u64) -> i32 {
raw_msgbox(msg, title, MsgBoxType::Warn, MsgBtnType::Ok, timeout_ms)
}
#[allow(dead_code)]
pub fn quest_msgbox_yesno(msg: impl ToString, title: impl ToString, timeout_ms: u64) -> i32 {
raw_msgbox(msg, title, MsgBoxType::Quest, MsgBtnType::YesNo, timeout_ms)
}
#[allow(dead_code)]
pub fn quest_msgbox_okcancel(msg: impl ToString, title: impl ToString, timeout_ms: u64) -> i32 {
raw_msgbox(
msg,
title,
MsgBoxType::Quest,
MsgBtnType::OkCancel,
timeout_ms,
)
}