Refactor out windows dialog code

This commit is contained in:
Caelan Sayler
2023-12-25 15:16:16 +00:00
parent 2e4db61f61
commit 9ade3a11d2
8 changed files with 181 additions and 106 deletions

12
src/Rust/Cargo.lock generated
View File

@@ -233,6 +233,7 @@ dependencies = [
"clap", "clap",
"codesign-verify", "codesign-verify",
"derivative", "derivative",
"enum-flags",
"file-rotate", "file-rotate",
"fs_extra", "fs_extra",
"glob", "glob",
@@ -366,6 +367,17 @@ dependencies = [
"generic-array", "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]] [[package]]
name = "errno" name = "errno"
version = "0.3.8" version = "0.3.8"

View File

@@ -61,6 +61,7 @@ file-rotate = "0.7"
derivative = "2.2" derivative = "2.2"
simple-stopwatch = "0.1.4" simple-stopwatch = "0.1.4"
glob = "0.3" glob = "0.3"
enum-flags = "0.3"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
fs_extra = "1.2" fs_extra = "1.2"

View File

@@ -42,7 +42,7 @@ pub fn uninstall(log_file: &PathBuf) -> Result<()> {
if result { if result {
info!("Finished successfully."); 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 { } else {
error!("Finished with errors."); error!("Finished with errors.");
shared::dialogs::show_uninstall_complete_with_errors_dialog(&app, &log_file); shared::dialogs::show_uninstall_complete_with_errors_dialog(&app, &log_file);

View File

@@ -62,7 +62,7 @@ fn main() -> Result<()> {
let res = run(&debug, &installto); let res = run(&debug, &installto);
if let Err(e) = &res { if let Err(e) = &res {
error!("An error has occurred: {}", e); 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?; 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); let setup_name = format!("{} Setup {}", app.title, app.version);
error!("Process install hook failed: {}", e); error!("Process install hook failed: {}", e);
let _ = tx.send(windows::splash::MSG_CLOSE); let _ = tx.send(windows::splash::MSG_CLOSE);
dialogs::show_warning( dialogs::show_warn(
format!("Installation has completed, but the application install hook failed ({}). It may not have installed correctly.", e), &setup_name,
setup_name, None,
format!("Installation has completed, but the application install hook failed ({}). It may not have installed correctly.", e).as_str(),
); );
} }

View File

@@ -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<bool> {
// 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<co::DLGID> {
// generate(title, header, body, None, co::TDCBF::YES | co::TDCBF::NO | co::TDCBF::CANCEL, co::TD_ICON::WARNING)
// }

View File

@@ -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,
}
}
}

View File

@@ -1,55 +1,10 @@
use super::bundle::Manifest; use super::{bundle::Manifest, dialogs_common::*, dialogs_const::*};
use std::{ use anyhow::Result;
path::PathBuf, use std::path::PathBuf;
sync::atomic::{AtomicBool, Ordering},
};
use winsafe::{self as w, co, prelude::*, WString}; 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<T: AsRef<str>, T2: AsRef<str>>(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<T: AsRef<str>, T2: AsRef<str>>(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<T: AsRef<str>, T2: AsRef<str>>(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) { pub fn show_restart_required(app: &Manifest) {
if get_silent() { show_warn(
return;
}
let hwnd = w::HWND::GetDesktopWindow();
let _ = warn(
&hwnd,
format!("{} Setup {}", app.title, app.version).as_str(), format!("{} Setup {}", app.title, app.version).as_str(),
Some("Restart Required"), Some("Restart Required"),
"A restart is required before Setup can continue. Please restart your computer and try again.", "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; return false;
} }
let hwnd = w::HWND::GetDesktopWindow(); show_ok_cancel(
ok_cancel(
&hwnd,
format!("{} Update", app.title).as_str(), format!("{} Update", app.title).as_str(),
Some(format!("{} would like to update from {} to {}", app.title, from, to).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(), format!("{} {to} has missing dependencies which need to be installed: {}, would you like to continue?", app.title, depedency_string).as_str(),
Some("Install & Update"), Some("Install & Update"),
) )
.unwrap_or(false)
} }
pub fn show_setup_missing_dependencies_dialog(app: &Manifest, depedency_string: &str) -> bool { 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; return true;
} }
let hwnd = w::HWND::GetDesktopWindow(); show_ok_cancel(
ok_cancel(
&hwnd,
format!("{} Setup {}", app.title, app.version).as_str(), format!("{} Setup {}", app.title, app.version).as_str(),
Some(format!("{} has missing system dependencies.", app.title).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(), format!("{} requires the following packages to be installed: {}, would you like to continue?", app.title, depedency_string).as_str(),
Some("Install"), Some("Install"),
) )
.unwrap_or(false)
} }
pub fn show_uninstall_complete_with_errors_dialog(app: &Manifest, log_path: &PathBuf) { 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 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<()> { pub fn generate(title: &str, header: Option<&str>, body: &str, ok_text: Option<&str>, btns: DialogButton, ico: DialogIcon) -> Result<DialogResult> {
generate(hparent, title, header, body, None, co::TDCBF::OK, co::TD_ICON::ERROR).map(|_| ()) let hparent = w::HWND::GetDesktopWindow();
}
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<bool> {
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<bool> {
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<co::DLGID> {
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<co::DLGID> {
let mut ok_text_buf = WString::from_opt_str(ok_text); let mut ok_text_buf = WString::from_opt_str(ok_text);
let mut custom_btns = if ok_text.is_some() { let mut custom_btns = if ok_text.is_some() {
let mut td_btn = w::TASKDIALOG_BUTTON::default(); let mut td_btn = w::TASKDIALOG_BUTTON::default();
td_btn.set_nButtonID(co::DLGID::OK.into()); td_btn.set_nButtonID(co::DLGID::OK.into());
td_btn.set_pszButtonText(Some(&mut ok_text_buf)); td_btn.set_pszButtonText(Some(&mut ok_text_buf));
let mut custom_btns = Vec::with_capacity(1); let mut custom_btns = Vec::with_capacity(1);
custom_btns.push(td_btn); custom_btns.push(td_btn);
custom_btns custom_btns
@@ -233,8 +144,8 @@ fn generate(
let mut tdc = w::TASKDIALOGCONFIG::default(); let mut tdc = w::TASKDIALOGCONFIG::default();
tdc.hwndParent = unsafe { hparent.raw_copy() }; tdc.hwndParent = unsafe { hparent.raw_copy() };
tdc.dwFlags = co::TDF::ALLOW_DIALOG_CANCELLATION | co::TDF::POSITION_RELATIVE_TO_WINDOW; tdc.dwFlags = co::TDF::ALLOW_DIALOG_CANCELLATION | co::TDF::POSITION_RELATIVE_TO_WINDOW;
tdc.dwCommonButtons = btns; tdc.dwCommonButtons = btns.to_win();
tdc.set_pszMainIcon(w::IconIdTdicon::Tdicon(ico)); tdc.set_pszMainIcon(w::IconIdTdicon::Tdicon(ico.to_win()));
if ok_text.is_some() { if ok_text.is_some() {
tdc.set_pButtons(Some(&mut custom_btns)); tdc.set_pButtons(Some(&mut custom_btns));
@@ -251,5 +162,6 @@ fn generate(
let mut body_buf = WString::from_str(body); let mut body_buf = WString::from_str(body);
tdc.set_pszContent(Some(&mut body_buf)); 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))
} }

View File

@@ -1,5 +1,16 @@
pub mod bundle; 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; mod util_common;
pub use util_common::*; pub use util_common::*;