diff --git a/src/Rust/Cargo.lock b/src/Rust/Cargo.lock index 94abb4fe..75b20cc5 100644 --- a/src/Rust/Cargo.lock +++ b/src/Rust/Cargo.lock @@ -233,6 +233,7 @@ dependencies = [ "clap", "codesign-verify", "derivative", + "enum-flags", "file-rotate", "fs_extra", "glob", @@ -366,6 +367,17 @@ dependencies = [ "generic-array", ] +[[package]] +name = "enum-flags" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e49d7f94ae5845d14507b85d9f24f1ac106acca19508cd8c78a54616f8610233" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "errno" version = "0.3.8" diff --git a/src/Rust/Cargo.toml b/src/Rust/Cargo.toml index 714e2328..1c999e4c 100644 --- a/src/Rust/Cargo.toml +++ b/src/Rust/Cargo.toml @@ -61,6 +61,7 @@ file-rotate = "0.7" derivative = "2.2" simple-stopwatch = "0.1.4" glob = "0.3" +enum-flags = "0.3" [target.'cfg(windows)'.dependencies] fs_extra = "1.2" diff --git a/src/Rust/src/commands/uninstall.rs b/src/Rust/src/commands/uninstall.rs index f6368053..bc378043 100644 --- a/src/Rust/src/commands/uninstall.rs +++ b/src/Rust/src/commands/uninstall.rs @@ -42,7 +42,7 @@ pub fn uninstall(log_file: &PathBuf) -> Result<()> { if result { info!("Finished successfully."); - shared::dialogs::show_info("The application was successfully uninstalled.", format!("{} Uninstall", app.title)); + shared::dialogs::show_info(format!("{} Uninstall", app.title).as_str(), None, "The application was successfully uninstalled."); } else { error!("Finished with errors."); shared::dialogs::show_uninstall_complete_with_errors_dialog(&app, &log_file); diff --git a/src/Rust/src/setup.rs b/src/Rust/src/setup.rs index 07a238a7..72277f75 100644 --- a/src/Rust/src/setup.rs +++ b/src/Rust/src/setup.rs @@ -62,7 +62,7 @@ fn main() -> Result<()> { let res = run(&debug, &installto); if let Err(e) = &res { error!("An error has occurred: {}", e); - dialogs::show_error(format!("An error has occurred: {}", e), "Setup Error".to_string()); + dialogs::show_error("Setup Error", None, format!("An error has occurred: {}", e).as_str()); } res?; @@ -241,9 +241,10 @@ fn install_app(pkg: &bundle::BundleInfo, root_path: &PathBuf, tx: &std::sync::mp let setup_name = format!("{} Setup {}", app.title, app.version); error!("Process install hook failed: {}", e); let _ = tx.send(windows::splash::MSG_CLOSE); - dialogs::show_warning( - format!("Installation has completed, but the application install hook failed ({}). It may not have installed correctly.", e), - setup_name, + dialogs::show_warn( + &setup_name, + None, + format!("Installation has completed, but the application install hook failed ({}). It may not have installed correctly.", e).as_str(), ); } diff --git a/src/Rust/src/shared/dialogs_common.rs b/src/Rust/src/shared/dialogs_common.rs new file mode 100644 index 00000000..b14f228a --- /dev/null +++ b/src/Rust/src/shared/dialogs_common.rs @@ -0,0 +1,47 @@ +use super::dialogs::generate; +use super::dialogs_const::*; +use std::sync::atomic::{AtomicBool, Ordering}; + +static SILENT: AtomicBool = AtomicBool::new(false); + +pub fn set_silent(silent: bool) { + SILENT.store(silent, Ordering::Relaxed); +} + +pub fn get_silent() -> bool { + SILENT.load(Ordering::Relaxed) +} + +pub fn show_error(title: &str, header: Option<&str>, body: &str) { + if !get_silent() { + let _ = generate(title, header, body, None, DialogButton::Ok, DialogIcon::Error).map(|_| ()); + } +} + +pub fn show_warn(title: &str, header: Option<&str>, body: &str) { + if !get_silent() { + let _ = generate(title, header, body, None, DialogButton::Ok, DialogIcon::Warning).map(|_| ()); + } +} + +pub fn show_info(title: &str, header: Option<&str>, body: &str) { + if !get_silent() { + let _ = generate(title, header, body, None, DialogButton::Ok, DialogIcon::Information).map(|_| ()); + } +} + +pub fn show_ok_cancel(title: &str, header: Option<&str>, body: &str, ok_text: Option<&str>) -> bool { + let mut btns = DialogButton::Cancel; + if ok_text.is_none() { + btns |= DialogButton::Ok; + } + generate(title, header, body, ok_text, btns, DialogIcon::Warning).map(|dlg_id| dlg_id == DialogResult::Ok).unwrap_or(false) +} + +// pub fn yes_no(title: &str, header: Option<&str>, body: &str) -> Result { +// generate(title, header, body, None, co::TDCBF::YES | co::TDCBF::NO, co::TD_ICON::WARNING).map(|dlg_id| dlg_id == co::DLGID::YES) +// } + +// pub fn yes_no_cancel(title: &str, header: Option<&str>, body: &str) -> Result { +// generate(title, header, body, None, co::TDCBF::YES | co::TDCBF::NO | co::TDCBF::CANCEL, co::TD_ICON::WARNING) +// } diff --git a/src/Rust/src/shared/dialogs_const.rs b/src/Rust/src/shared/dialogs_const.rs new file mode 100644 index 00000000..7d80fa3d --- /dev/null +++ b/src/Rust/src/shared/dialogs_const.rs @@ -0,0 +1,91 @@ +use enum_flags::enum_flags; + +#[enum_flags] +#[derive(PartialEq, Clone, Copy, strum::IntoStaticStr)] +#[repr(u8)] +pub enum DialogButton { + Ok = 1, + Yes = 2, + No = 4, + Cancel = 8, + Retry = 16, + Close = 32, +} + +#[derive(PartialEq, Clone, Copy, strum::IntoStaticStr)] +#[repr(u8)] +pub enum DialogIcon { + Warning = 1, + Error = 2, + Information = 4, +} + +#[derive(PartialEq, Clone, Copy, strum::IntoStaticStr)] +#[repr(u8)] +pub enum DialogResult { + Unknown = 0, + Ok = 1, + Cancel = 2, + Abort = 3, + Retry = 4, + Ignore = 5, + Yes = 6, + No = 7, + Tryagain = 10, + Continue = 11, +} + +#[cfg(target_os = "windows")] +impl DialogButton { + pub fn to_win(&self) -> winsafe::co::TDCBF { + let mut result = unsafe { winsafe::co::TDCBF::from_raw(0) }; + if self.has_ok() { + result |= winsafe::co::TDCBF::OK; + } + if self.has_yes() { + result |= winsafe::co::TDCBF::YES; + } + if self.has_no() { + result |= winsafe::co::TDCBF::NO; + } + if self.has_cancel() { + result |= winsafe::co::TDCBF::CANCEL; + } + if self.has_retry() { + result |= winsafe::co::TDCBF::RETRY; + } + if self.has_close() { + result |= winsafe::co::TDCBF::CLOSE; + } + result + } +} + +#[cfg(target_os = "windows")] +impl DialogIcon { + pub fn to_win(&self) -> winsafe::co::TD_ICON { + match self { + DialogIcon::Warning => winsafe::co::TD_ICON::WARNING, + DialogIcon::Error => winsafe::co::TD_ICON::ERROR, + DialogIcon::Information => winsafe::co::TD_ICON::INFORMATION, + } + } +} + +#[cfg(target_os = "windows")] +impl DialogResult { + pub fn from_win(dlg_id: winsafe::co::DLGID) -> DialogResult { + match dlg_id { + winsafe::co::DLGID::OK => DialogResult::Ok, + winsafe::co::DLGID::CANCEL => DialogResult::Cancel, + winsafe::co::DLGID::ABORT => DialogResult::Abort, + winsafe::co::DLGID::RETRY => DialogResult::Retry, + winsafe::co::DLGID::IGNORE => DialogResult::Ignore, + winsafe::co::DLGID::YES => DialogResult::Yes, + winsafe::co::DLGID::NO => DialogResult::No, + winsafe::co::DLGID::TRYAGAIN => DialogResult::Tryagain, + winsafe::co::DLGID::CONTINUE => DialogResult::Continue, + _ => DialogResult::Unknown, + } + } +} diff --git a/src/Rust/src/shared/dialogs.rs b/src/Rust/src/shared/dialogs_windows.rs similarity index 68% rename from src/Rust/src/shared/dialogs.rs rename to src/Rust/src/shared/dialogs_windows.rs index d6cf6e4c..852ee9e1 100644 --- a/src/Rust/src/shared/dialogs.rs +++ b/src/Rust/src/shared/dialogs_windows.rs @@ -1,55 +1,10 @@ -use super::bundle::Manifest; -use std::{ - path::PathBuf, - sync::atomic::{AtomicBool, Ordering}, -}; +use super::{bundle::Manifest, dialogs_common::*, dialogs_const::*}; +use anyhow::Result; +use std::path::PathBuf; use winsafe::{self as w, co, prelude::*, WString}; -static SILENT: AtomicBool = AtomicBool::new(false); - -pub fn set_silent(silent: bool) { - SILENT.store(silent, Ordering::Relaxed); -} - -pub fn get_silent() -> bool { - SILENT.load(Ordering::Relaxed) -} - -pub fn show_error, T2: AsRef>(err: T, title: T2) { - if get_silent() { - return; - } - let err = err.as_ref(); - let title = title.as_ref(); - let _ = w::HWND::GetDesktopWindow().MessageBox(err, title, co::MB::ICONERROR); -} - -pub fn show_info, T2: AsRef>(info: T, title: T2) { - if get_silent() { - return; - } - let info = info.as_ref(); - let title = title.as_ref(); - let _ = w::HWND::GetDesktopWindow().MessageBox(info, title, co::MB::ICONINFORMATION); -} - -pub fn show_warning, T2: AsRef>(warning: T, title: T2) { - if get_silent() { - return; - } - let warning = warning.as_ref(); - let title = title.as_ref(); - let _ = w::HWND::GetDesktopWindow().MessageBox(warning, title, co::MB::ICONWARNING); -} - pub fn show_restart_required(app: &Manifest) { - if get_silent() { - return; - } - - let hwnd = w::HWND::GetDesktopWindow(); - let _ = warn( - &hwnd, + show_warn( format!("{} Setup {}", app.title, app.version).as_str(), Some("Restart Required"), "A restart is required before Setup can continue. Please restart your computer and try again.", @@ -65,15 +20,12 @@ pub fn show_update_missing_dependencies_dialog(app: &Manifest, depedency_string: return false; } - let hwnd = w::HWND::GetDesktopWindow(); - ok_cancel( - &hwnd, + show_ok_cancel( format!("{} Update", app.title).as_str(), Some(format!("{} would like to update from {} to {}", app.title, from, to).as_str()), format!("{} {to} has missing dependencies which need to be installed: {}, would you like to continue?", app.title, depedency_string).as_str(), Some("Install & Update"), ) - .unwrap_or(false) } pub fn show_setup_missing_dependencies_dialog(app: &Manifest, depedency_string: &str) -> bool { @@ -81,15 +33,12 @@ pub fn show_setup_missing_dependencies_dialog(app: &Manifest, depedency_string: return true; } - let hwnd = w::HWND::GetDesktopWindow(); - ok_cancel( - &hwnd, + show_ok_cancel( format!("{} Setup {}", app.title, app.version).as_str(), Some(format!("{} has missing system dependencies.", app.title).as_str()), format!("{} requires the following packages to be installed: {}, would you like to continue?", app.title, depedency_string).as_str(), Some("Install"), ) - .unwrap_or(false) } pub fn show_uninstall_complete_with_errors_dialog(app: &Manifest, log_path: &PathBuf) { @@ -178,51 +127,13 @@ extern "system" fn task_dialog_callback(_: w::HWND, msg: co::TDN, _: usize, _: i return co::HRESULT::S_OK; // close dialog on button press } -pub fn error(hparent: &w::HWND, title: &str, header: Option<&str>, body: &str) -> w::HrResult<()> { - generate(hparent, title, header, body, None, co::TDCBF::OK, co::TD_ICON::ERROR).map(|_| ()) -} - -pub fn warn(hparent: &w::HWND, title: &str, header: Option<&str>, body: &str) -> w::HrResult<()> { - generate(hparent, title, header, body, None, co::TDCBF::OK, co::TD_ICON::WARNING).map(|_| ()) -} - -pub fn info(hparent: &w::HWND, title: &str, header: Option<&str>, body: &str) -> w::HrResult<()> { - generate(hparent, title, header, body, None, co::TDCBF::OK, co::TD_ICON::INFORMATION).map(|_| ()) -} - -#[must_use] -pub fn ok_cancel(hparent: &w::HWND, title: &str, header: Option<&str>, body: &str, ok_text: Option<&str>) -> w::HrResult { - let mut btns = co::TDCBF::CANCEL; - if ok_text.is_none() { - btns |= co::TDCBF::OK; - } - - generate(hparent, title, header, body, ok_text, btns, co::TD_ICON::WARNING).map(|dlg_id| dlg_id == co::DLGID::OK) -} - -pub fn yes_no(hparent: &w::HWND, title: &str, header: Option<&str>, body: &str) -> w::HrResult { - generate(hparent, title, header, body, None, co::TDCBF::YES | co::TDCBF::NO, co::TD_ICON::WARNING).map(|dlg_id| dlg_id == co::DLGID::YES) -} - -pub fn yes_no_cancel(hparent: &w::HWND, title: &str, header: Option<&str>, body: &str) -> w::HrResult { - generate(hparent, title, header, body, None, co::TDCBF::YES | co::TDCBF::NO | co::TDCBF::CANCEL, co::TD_ICON::WARNING) -} - -fn generate( - hparent: &w::HWND, - title: &str, - header: Option<&str>, - body: &str, - ok_text: Option<&str>, - btns: co::TDCBF, - ico: co::TD_ICON, -) -> w::HrResult { +pub fn generate(title: &str, header: Option<&str>, body: &str, ok_text: Option<&str>, btns: DialogButton, ico: DialogIcon) -> Result { + let hparent = w::HWND::GetDesktopWindow(); let mut ok_text_buf = WString::from_opt_str(ok_text); let mut custom_btns = if ok_text.is_some() { let mut td_btn = w::TASKDIALOG_BUTTON::default(); td_btn.set_nButtonID(co::DLGID::OK.into()); td_btn.set_pszButtonText(Some(&mut ok_text_buf)); - let mut custom_btns = Vec::with_capacity(1); custom_btns.push(td_btn); custom_btns @@ -233,8 +144,8 @@ fn generate( let mut tdc = w::TASKDIALOGCONFIG::default(); tdc.hwndParent = unsafe { hparent.raw_copy() }; tdc.dwFlags = co::TDF::ALLOW_DIALOG_CANCELLATION | co::TDF::POSITION_RELATIVE_TO_WINDOW; - tdc.dwCommonButtons = btns; - tdc.set_pszMainIcon(w::IconIdTdicon::Tdicon(ico)); + tdc.dwCommonButtons = btns.to_win(); + tdc.set_pszMainIcon(w::IconIdTdicon::Tdicon(ico.to_win())); if ok_text.is_some() { tdc.set_pButtons(Some(&mut custom_btns)); @@ -251,5 +162,6 @@ fn generate( let mut body_buf = WString::from_str(body); tdc.set_pszContent(Some(&mut body_buf)); - w::TaskDialogIndirect(&tdc, None).map(|(dlg_id, _)| dlg_id) + let result = w::TaskDialogIndirect(&tdc, None).map(|(dlg_id, _)| dlg_id)?; + Ok(DialogResult::from_win(result)) } diff --git a/src/Rust/src/shared/mod.rs b/src/Rust/src/shared/mod.rs index e0dab810..9ff05a07 100644 --- a/src/Rust/src/shared/mod.rs +++ b/src/Rust/src/shared/mod.rs @@ -1,5 +1,16 @@ pub mod bundle; -pub mod dialogs; + +mod dialogs_const; +mod dialogs_common; +#[cfg(target_os = "windows")] +mod dialogs_windows; + +pub mod dialogs { + pub use super::dialogs_const::*; + pub use super::dialogs_common::*; + #[cfg(target_os = "windows")] + pub use super::dialogs_windows::*; +} mod util_common; pub use util_common::*;