mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
Remove winsafe crate and migrate everything to windows-rs
This commit is contained in:
1
.github/renovate.json5
vendored
1
.github/renovate.json5
vendored
@@ -63,7 +63,6 @@
|
||||
{
|
||||
"groupName": "frozen",
|
||||
"matchPackageNames": [
|
||||
"winsafe", // newer versions causes runtime errors in release builds
|
||||
"System.CommandLine", // too many breaking changes too frequently
|
||||
"xunit.runner.visualstudio", // 20-12-2024: broke tests (something about sn signing maybe?)
|
||||
"Microsoft.NET.Test.Sdk", // 23-05-2025: 17.13.0 was the last version which supported net6
|
||||
|
||||
18
Cargo.lock
generated
18
Cargo.lock
generated
@@ -2328,8 +2328,8 @@ dependencies = [
|
||||
"walkdir",
|
||||
"webview2-com-sys",
|
||||
"windows",
|
||||
"winreg",
|
||||
"winres",
|
||||
"winsafe",
|
||||
"zip",
|
||||
"zstd",
|
||||
]
|
||||
@@ -2844,6 +2844,16 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.55.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winres"
|
||||
version = "0.1.12"
|
||||
@@ -2853,12 +2863,6 @@ dependencies = [
|
||||
"toml 0.5.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winsafe"
|
||||
version = "0.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a40369220be405a294b88b13ccc3d916fac12423250b30092c8c4ea19001f7f1"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.39.0"
|
||||
|
||||
@@ -77,7 +77,6 @@ fs_extra = "1.3"
|
||||
memmap2 = "0.9"
|
||||
windows = "0.61"
|
||||
webview2-com-sys = "0.37"
|
||||
winsafe = { version = "0.0.20", features = ["gui"] }
|
||||
cbindgen = "0.28"
|
||||
log-panics = "2.1.0"
|
||||
core-foundation = "0.10"
|
||||
@@ -87,9 +86,7 @@ walkdir = "2.5"
|
||||
rayon = "1.6"
|
||||
progress-streams = "1.1"
|
||||
flate2 = { version = "1.0", default-features = false }
|
||||
# mtzip = "=4.0.2"
|
||||
# ripunzip = "=2.0.1"
|
||||
# zerofrom = "=0.1.5"
|
||||
winreg = "0.55"
|
||||
|
||||
# default to small, optimized workspace release binaries
|
||||
[profile.release]
|
||||
|
||||
@@ -85,7 +85,6 @@ waitpid-any.workspace = true
|
||||
fs_extra.workspace = true
|
||||
memmap2.workspace = true
|
||||
image.workspace = true
|
||||
winsafe.workspace = true
|
||||
windows = { workspace = true, features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_Security",
|
||||
@@ -93,23 +92,26 @@ windows = { workspace = true, features = [
|
||||
"Win32_Globalization",
|
||||
"Win32_UI",
|
||||
"Win32_UI_Shell",
|
||||
"Win32_UI_Shell_Common",
|
||||
"Win32_UI_Shell_PropertiesSystem",
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
"Win32_UI_Controls",
|
||||
"Win32_Graphics",
|
||||
"Win32_Graphics_Gdi",
|
||||
"Win32_System_Threading",
|
||||
"Win32_System_SystemInformation",
|
||||
"Win32_System_Variant",
|
||||
"Win32_System_Environment",
|
||||
"Win32_Storage_EnhancedStorage",
|
||||
"Win32_Storage_FileSystem",
|
||||
"Win32_System_Com_StructuredStorage",
|
||||
"Win32_System_Registry",
|
||||
"Win32_System_Threading",
|
||||
"Win32_System_ProcessStatus",
|
||||
"Win32_System_WindowsProgramming",
|
||||
"Win32_System_LibraryLoader",
|
||||
"Win32_UI_Shell_Common",
|
||||
"Win32_UI_Shell_PropertiesSystem",
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
"Win32_System_ApplicationInstallationAndServicing",
|
||||
"Win32_System_Kernel",
|
||||
"Win32_Storage_EnhancedStorage",
|
||||
"Win32_Storage_FileSystem",
|
||||
"Wdk",
|
||||
"Wdk_System",
|
||||
"Wdk_System_Threading",
|
||||
@@ -118,6 +120,7 @@ webview2-com-sys.workspace = true
|
||||
libloading.workspace = true
|
||||
strsim.workspace = true
|
||||
same-file.workspace = true
|
||||
winreg.workspace = true
|
||||
# filelocksmith.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -79,7 +79,7 @@ fn test_show_all_dialogs() {
|
||||
show_warn("Warning", None, "This is a warning.");
|
||||
show_info("Information", None, "This is information.");
|
||||
assert!(show_ok_cancel("Ok/Cancel", None, "This is a question.", None));
|
||||
assert!(!show_ok_cancel("Ok/Cancel", None, "This is a question.", Some("Ok")));
|
||||
assert!(!show_ok_cancel("Ok/Cancel", None, "This is a question.", Some("Dont click!")));
|
||||
}
|
||||
|
||||
// pub fn yes_no(title: &str, header: Option<&str>, body: &str) -> Result<bool> {
|
||||
|
||||
@@ -37,25 +37,26 @@ pub enum DialogResult {
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
impl DialogButton {
|
||||
pub fn to_win(&self) -> winsafe::co::TDCBF {
|
||||
let mut result = unsafe { winsafe::co::TDCBF::from_raw(0) };
|
||||
pub fn to_win(&self) -> windows::Win32::UI::Controls::TASKDIALOG_COMMON_BUTTON_FLAGS {
|
||||
use windows::Win32::UI::Controls::*;
|
||||
let mut result = TASKDIALOG_COMMON_BUTTON_FLAGS(0);
|
||||
if self.has_ok() {
|
||||
result |= winsafe::co::TDCBF::OK;
|
||||
result |= TDCBF_OK_BUTTON;
|
||||
}
|
||||
if self.has_yes() {
|
||||
result |= winsafe::co::TDCBF::YES;
|
||||
result |= TDCBF_YES_BUTTON;
|
||||
}
|
||||
if self.has_no() {
|
||||
result |= winsafe::co::TDCBF::NO;
|
||||
result |= TDCBF_NO_BUTTON;
|
||||
}
|
||||
if self.has_cancel() {
|
||||
result |= winsafe::co::TDCBF::CANCEL;
|
||||
result |= TDCBF_CANCEL_BUTTON;
|
||||
}
|
||||
if self.has_retry() {
|
||||
result |= winsafe::co::TDCBF::RETRY;
|
||||
result |= TDCBF_RETRY_BUTTON;
|
||||
}
|
||||
if self.has_close() {
|
||||
result |= winsafe::co::TDCBF::CLOSE;
|
||||
result |= TDCBF_CLOSE_BUTTON;
|
||||
}
|
||||
result
|
||||
}
|
||||
@@ -63,28 +64,31 @@ impl DialogButton {
|
||||
|
||||
impl DialogIcon {
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn to_win(&self) -> winsafe::co::TD_ICON {
|
||||
pub fn to_win(&self) -> windows::core::PCWSTR {
|
||||
use windows::Win32::UI::Controls::*;
|
||||
match self {
|
||||
DialogIcon::Warning => winsafe::co::TD_ICON::WARNING,
|
||||
DialogIcon::Error => winsafe::co::TD_ICON::ERROR,
|
||||
DialogIcon::Information => winsafe::co::TD_ICON::INFORMATION,
|
||||
DialogIcon::Warning => TD_WARNING_ICON,
|
||||
DialogIcon::Error => TD_ERROR_ICON,
|
||||
DialogIcon::Information => TD_INFORMATION_ICON,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
impl DialogResult {
|
||||
pub fn from_win(dlg_id: winsafe::co::DLGID) -> DialogResult {
|
||||
pub fn from_win(dlg_id: i32) -> DialogResult {
|
||||
use windows::Win32::UI::WindowsAndMessaging::*;
|
||||
let dlg_id = MESSAGEBOX_RESULT(dlg_id);
|
||||
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,
|
||||
IDOK => DialogResult::Ok,
|
||||
IDCANCEL => DialogResult::Cancel,
|
||||
IDABORT => DialogResult::Abort,
|
||||
IDRETRY => DialogResult::Retry,
|
||||
IDIGNORE => DialogResult::Ignore,
|
||||
IDYES => DialogResult::Yes,
|
||||
IDNO => DialogResult::No,
|
||||
IDTRYAGAIN => DialogResult::Tryagain,
|
||||
IDCONTINUE => DialogResult::Continue,
|
||||
_ => DialogResult::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,22 @@
|
||||
use super::{dialogs_common::*, dialogs_const::*};
|
||||
use velopack::bundle::Manifest;
|
||||
use crate::windows::strings::string_to_wide;
|
||||
use anyhow::Result;
|
||||
use std::path::PathBuf;
|
||||
use winsafe::{self as w, co, prelude::*, WString};
|
||||
use velopack::locator::{auto_locate_app_manifest, LocationContext};
|
||||
use velopack::{
|
||||
bundle::Manifest,
|
||||
locator::{auto_locate_app_manifest, LocationContext},
|
||||
};
|
||||
use windows::{
|
||||
core::HRESULT,
|
||||
Win32::{
|
||||
Foundation::{FALSE, HWND, LPARAM, S_FALSE, S_OK, WPARAM},
|
||||
UI::{
|
||||
Controls::*,
|
||||
Shell::ShellExecuteW,
|
||||
WindowsAndMessaging::{GetDesktopWindow, IDCANCEL, IDCONTINUE, IDOK, IDRETRY, IDYES, SW_SHOWDEFAULT},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
pub fn show_restart_required(app: &Manifest) {
|
||||
show_warn(
|
||||
@@ -58,32 +71,33 @@ pub fn show_uninstall_complete_with_errors_dialog(app_title: &str, log_path: Opt
|
||||
return;
|
||||
}
|
||||
|
||||
let mut setup_name = WString::from_str(format!("{} Uninstall", app_title));
|
||||
let mut instruction = WString::from_str(format!("{} uninstall has completed with errors.", app_title));
|
||||
let mut content = WString::from_str(
|
||||
let setup_name = string_to_wide(format!("{} Uninstall", app_title));
|
||||
let instruction = string_to_wide(format!("{} uninstall has completed with errors.", app_title));
|
||||
let content = string_to_wide(
|
||||
"There may be left-over files or directories on your system. You can attempt to remove these manually or re-install the application and try again.",
|
||||
);
|
||||
|
||||
let mut config: w::TASKDIALOGCONFIG = Default::default();
|
||||
config.dwFlags = co::TDF::ENABLE_HYPERLINKS | co::TDF::SIZE_TO_CONTENT;
|
||||
config.dwCommonButtons = co::TDCBF::OK;
|
||||
config.set_pszMainIcon(w::IconIdTdicon::Tdicon(co::TD_ICON::WARNING));
|
||||
config.set_pszWindowTitle(Some(&mut setup_name));
|
||||
config.set_pszMainInstruction(Some(&mut instruction));
|
||||
config.set_pszContent(Some(&mut content));
|
||||
let mut config = TASKDIALOGCONFIG::default();
|
||||
config.cbSize = std::mem::size_of::<TASKDIALOGCONFIG>() as u32;
|
||||
config.dwFlags = TDF_ENABLE_HYPERLINKS | TDF_SIZE_TO_CONTENT;
|
||||
config.dwCommonButtons = TDCBF_OK_BUTTON;
|
||||
config.pszWindowTitle = setup_name.as_pcwstr();
|
||||
config.pszMainInstruction = instruction.as_pcwstr();
|
||||
config.pszContent = content.as_pcwstr();
|
||||
config.Anonymous1.pszMainIcon = TD_WARNING_ICON;
|
||||
|
||||
let footer_path = log_path.map(|p| p.to_string_lossy().to_string()).unwrap_or("".to_string());
|
||||
let mut footer = WString::from_str(format!("Log file: '<A HREF=\"na\">{}</A>'", footer_path));
|
||||
let footer = string_to_wide(format!("Log file: '<A HREF=\"na\">{}</A>'", footer_path));
|
||||
if let Some(log_path) = log_path {
|
||||
if log_path.exists() {
|
||||
config.set_pszFooterIcon(w::IconId::Id(co::TD_ICON::INFORMATION.into()));
|
||||
config.set_pszFooter(Some(&mut footer));
|
||||
config.lpCallbackData = log_path as *const PathBuf as usize;
|
||||
config.Anonymous2.pszFooterIcon = TD_INFORMATION_ICON;
|
||||
config.pszFooter = footer.as_pcwstr();
|
||||
config.lpCallbackData = log_path as *const PathBuf as isize;
|
||||
config.pfCallback = Some(task_dialog_callback);
|
||||
}
|
||||
}
|
||||
|
||||
let _ = w::TaskDialogIndirect(&config, None);
|
||||
unsafe { TaskDialogIndirect(&config, None, None, None).ok() };
|
||||
}
|
||||
|
||||
pub fn show_processes_locking_folder_dialog(app_title: &str, app_version: &str, process_names: &str) -> DialogResult {
|
||||
@@ -91,42 +105,38 @@ pub fn show_processes_locking_folder_dialog(app_title: &str, app_version: &str,
|
||||
return DialogResult::Cancel;
|
||||
}
|
||||
|
||||
let mut config: w::TASKDIALOGCONFIG = Default::default();
|
||||
config.set_pszMainIcon(w::IconIdTdicon::Tdicon(co::TD_ICON::INFORMATION));
|
||||
let mut config = TASKDIALOGCONFIG::default();
|
||||
config.cbSize = std::mem::size_of::<TASKDIALOGCONFIG>() as u32;
|
||||
config.Anonymous1.pszMainIcon = TD_INFORMATION_ICON;
|
||||
|
||||
let mut update_name = WString::from_str(format!("{} Update {}", app_title, app_version));
|
||||
let mut instruction = WString::from_str(format!("{} Update", app_title));
|
||||
|
||||
let mut content = WString::from_str(format!(
|
||||
let update_name = string_to_wide(format!("{} Update {}", app_title, app_version));
|
||||
let instruction = string_to_wide(format!("{} Update", app_title));
|
||||
let content = string_to_wide(format!(
|
||||
"There are programs ({}) preventing the {} update from proceeding. \n\n\
|
||||
You can press Continue to have this updater attempt to close them automatically, or if you've closed them yourself press Retry for the updater to check again.",
|
||||
process_names, app_title));
|
||||
|
||||
let mut btn_retry_txt = WString::from_str("Retry\nTry again if you've closed the program(s)");
|
||||
let mut btn_continue_txt = WString::from_str("Continue\nAttempt to close the program(s) automatically");
|
||||
let mut btn_cancel_txt = WString::from_str("Cancel\nThe update will not continue");
|
||||
let btn_retry_txt = string_to_wide("Retry\nTry again if you've closed the program(s)");
|
||||
let btn_continue_txt = string_to_wide("Continue\nAttempt to close the program(s) automatically");
|
||||
let btn_cancel_txt = string_to_wide("Cancel\nThe update will not continue");
|
||||
let btn_retry = TASKDIALOG_BUTTON { nButtonID: IDRETRY.0, pszButtonText: btn_retry_txt.as_pcwstr() };
|
||||
let btn_continue = TASKDIALOG_BUTTON { nButtonID: IDCONTINUE.0, pszButtonText: btn_continue_txt.as_pcwstr() };
|
||||
let btn_cancel = TASKDIALOG_BUTTON { nButtonID: IDCANCEL.0, pszButtonText: btn_cancel_txt.as_pcwstr() };
|
||||
let custom_btns = vec![btn_retry, btn_continue, btn_cancel];
|
||||
|
||||
let mut btn_retry = w::TASKDIALOG_BUTTON::default();
|
||||
btn_retry.set_nButtonID(co::DLGID::RETRY.into());
|
||||
btn_retry.set_pszButtonText(Some(&mut btn_retry_txt));
|
||||
config.dwFlags = TDF_USE_COMMAND_LINKS;
|
||||
config.cButtons = custom_btns.len() as u32;
|
||||
config.pButtons = custom_btns.as_ptr();
|
||||
config.pszWindowTitle = update_name.as_pcwstr();
|
||||
config.pszMainInstruction = instruction.as_pcwstr();
|
||||
config.pszContent = content.as_pcwstr();
|
||||
|
||||
let mut btn_continue = w::TASKDIALOG_BUTTON::default();
|
||||
btn_continue.set_nButtonID(co::DLGID::CONTINUE.into());
|
||||
btn_continue.set_pszButtonText(Some(&mut btn_continue_txt));
|
||||
let mut pnbutton = 0;
|
||||
let mut pnradiobutton = 0;
|
||||
let mut pfverificationflagchecked = FALSE;
|
||||
|
||||
let mut btn_cancel = w::TASKDIALOG_BUTTON::default();
|
||||
btn_cancel.set_nButtonID(co::DLGID::CANCEL.into());
|
||||
btn_cancel.set_pszButtonText(Some(&mut btn_cancel_txt));
|
||||
|
||||
let mut custom_btns = vec![btn_retry, btn_continue, btn_cancel];
|
||||
config.dwFlags = co::TDF::USE_COMMAND_LINKS;
|
||||
config.set_pButtons(Some(&mut custom_btns));
|
||||
config.set_pszWindowTitle(Some(&mut update_name));
|
||||
config.set_pszMainInstruction(Some(&mut instruction));
|
||||
config.set_pszContent(Some(&mut content));
|
||||
|
||||
let (btn, _) = w::TaskDialogIndirect(&config, None).ok().unwrap_or((co::DLGID::CANCEL, 0));
|
||||
DialogResult::from_win(btn)
|
||||
unsafe { TaskDialogIndirect(&config, Some(&mut pnbutton), Some(&mut pnradiobutton), Some(&mut pfverificationflagchecked)).ok() };
|
||||
DialogResult::from_win(pnbutton)
|
||||
}
|
||||
|
||||
pub fn show_overwrite_repair_dialog(app: &Manifest, root_path: &PathBuf, root_is_default: bool) -> bool {
|
||||
@@ -134,79 +144,75 @@ pub fn show_overwrite_repair_dialog(app: &Manifest, root_path: &PathBuf, root_is
|
||||
return true;
|
||||
}
|
||||
|
||||
// these are the defaults, if we can't detect the current app version - we call it "Repair"
|
||||
let mut config: w::TASKDIALOGCONFIG = Default::default();
|
||||
config.set_pszMainIcon(w::IconIdTdicon::Tdicon(co::TD_ICON::WARNING));
|
||||
let mut config = TASKDIALOGCONFIG::default();
|
||||
config.cbSize = std::mem::size_of::<TASKDIALOGCONFIG>() as u32;
|
||||
config.Anonymous1.pszMainIcon = TD_WARNING_ICON;
|
||||
|
||||
let mut setup_name = WString::from_str(format!("{} Setup {}", app.title, app.version));
|
||||
let mut instruction = WString::from_str(format!("{} is already installed.", app.title));
|
||||
let mut content = WString::from_str(
|
||||
"This application is installed on your computer. If it is not functioning correctly, you can attempt to repair it.",
|
||||
);
|
||||
let mut btn_yes_txt = WString::from_str(format!("Repair\nErase the application and re-install version {}.", app.version));
|
||||
let mut btn_cancel_txt = WString::from_str("Cancel\nBackup or save your work first");
|
||||
let setup_name = string_to_wide(format!("{} Setup {}", app.title, app.version));
|
||||
let mut instruction = string_to_wide(format!("{} is already installed.", app.title));
|
||||
let mut content =
|
||||
string_to_wide("This application is installed on your computer. If it is not functioning correctly, you can attempt to repair it.");
|
||||
let mut btn_yes_txt = string_to_wide(format!("Repair\nErase the application and re-install version {}.", app.version));
|
||||
let btn_cancel_txt = string_to_wide("Cancel\nBackup or save your work first");
|
||||
|
||||
// if we can detect the current app version, we call it "Update" or "Downgrade"
|
||||
let old_app = auto_locate_app_manifest(LocationContext::FromSpecifiedRootDir(root_path.to_owned()));
|
||||
if let Ok(old) = old_app {
|
||||
let old_version = old.get_manifest_version();
|
||||
if old_version < app.version {
|
||||
instruction = WString::from_str(format!("An older version of {} is installed.", app.title));
|
||||
content = WString::from_str(format!("Would you like to update from {} to {}?", old_version, app.version));
|
||||
btn_yes_txt = WString::from_str(format!("Update\nTo version {}", app.version));
|
||||
config.set_pszMainIcon(w::IconIdTdicon::Tdicon(co::TD_ICON::INFORMATION));
|
||||
instruction = string_to_wide(format!("An older version of {} is installed.", app.title));
|
||||
content = string_to_wide(format!("Would you like to update from {} to {}?", old_version, app.version));
|
||||
btn_yes_txt = string_to_wide(format!("Update\nTo version {}", app.version));
|
||||
config.Anonymous1.pszMainIcon = TD_INFORMATION_ICON;
|
||||
} else if old_version > app.version {
|
||||
instruction = WString::from_str(format!("A newer version of {} is installed.", app.title));
|
||||
content = WString::from_str(format!(
|
||||
instruction = string_to_wide(format!("A newer version of {} is installed.", app.title));
|
||||
content = string_to_wide(format!(
|
||||
"You already have {} installed. Would you like to downgrade this application to an older version?",
|
||||
old_version
|
||||
));
|
||||
btn_yes_txt = WString::from_str(format!("Downgrade\nTo version {}", app.version));
|
||||
btn_yes_txt = string_to_wide(format!("Downgrade\nTo version {}", app.version));
|
||||
}
|
||||
}
|
||||
|
||||
let mut footer = if root_is_default {
|
||||
WString::from_str(format!("The install directory is '<A HREF=\"na\">%LocalAppData%\\{}</A>'", app.id))
|
||||
let footer = if root_is_default {
|
||||
string_to_wide(format!("The install directory is '<A HREF=\"na\">%LocalAppData%\\{}</A>'", app.id))
|
||||
} else {
|
||||
WString::from_str(format!("The install directory is '<A HREF=\"na\">{}</A>'", root_path.display()))
|
||||
string_to_wide(format!("The install directory is '<A HREF=\"na\">{}</A>'", root_path.display()))
|
||||
};
|
||||
|
||||
let mut btn_yes = w::TASKDIALOG_BUTTON::default();
|
||||
btn_yes.set_nButtonID(co::DLGID::YES.into());
|
||||
btn_yes.set_pszButtonText(Some(&mut btn_yes_txt));
|
||||
let btn_yes = TASKDIALOG_BUTTON { nButtonID: IDYES.0, pszButtonText: btn_yes_txt.as_pcwstr() };
|
||||
let btn_cancel = TASKDIALOG_BUTTON { nButtonID: IDCANCEL.0, pszButtonText: btn_cancel_txt.as_pcwstr() };
|
||||
let custom_btns = vec![btn_yes, btn_cancel];
|
||||
|
||||
let mut btn_cancel = w::TASKDIALOG_BUTTON::default();
|
||||
btn_cancel.set_nButtonID(co::DLGID::CANCEL.into());
|
||||
btn_cancel.set_pszButtonText(Some(&mut btn_cancel_txt));
|
||||
config.dwFlags = TDF_ENABLE_HYPERLINKS | TDF_USE_COMMAND_LINKS;
|
||||
config.cButtons = custom_btns.len() as u32;
|
||||
config.pButtons = custom_btns.as_ptr();
|
||||
config.pszWindowTitle = setup_name.as_pcwstr();
|
||||
config.pszMainInstruction = instruction.as_pcwstr();
|
||||
config.pszContent = content.as_pcwstr();
|
||||
config.Anonymous2.pszFooterIcon = TD_INFORMATION_ICON;
|
||||
config.pszFooter = footer.as_pcwstr();
|
||||
|
||||
let mut custom_btns = Vec::with_capacity(2);
|
||||
custom_btns.push(btn_yes);
|
||||
custom_btns.push(btn_cancel);
|
||||
|
||||
config.dwFlags = co::TDF::ENABLE_HYPERLINKS | co::TDF::USE_COMMAND_LINKS;
|
||||
config.set_pButtons(Some(&mut custom_btns));
|
||||
config.set_pszWindowTitle(Some(&mut setup_name));
|
||||
config.set_pszMainInstruction(Some(&mut instruction));
|
||||
config.set_pszContent(Some(&mut content));
|
||||
config.set_pszFooterIcon(w::IconId::Id(co::TD_ICON::INFORMATION.into()));
|
||||
config.set_pszFooter(Some(&mut footer));
|
||||
|
||||
config.lpCallbackData = root_path as *const PathBuf as usize;
|
||||
config.lpCallbackData = root_path as *const PathBuf as isize;
|
||||
config.pfCallback = Some(task_dialog_callback);
|
||||
|
||||
let (btn, _) = w::TaskDialogIndirect(&config, None).ok().unwrap_or_else(|| (co::DLGID::YES, 0));
|
||||
return btn == co::DLGID::YES;
|
||||
let mut pnbutton = 0;
|
||||
let mut pnradiobutton = 0;
|
||||
let mut pfverificationflagchecked = FALSE;
|
||||
unsafe { TaskDialogIndirect(&config, Some(&mut pnbutton), Some(&mut pnradiobutton), Some(&mut pfverificationflagchecked)).ok() };
|
||||
pnbutton == IDYES.0
|
||||
}
|
||||
|
||||
extern "system" fn task_dialog_callback(_: w::HWND, msg: co::TDN, _: usize, _: isize, lp_ref_data: usize) -> co::HRESULT {
|
||||
if msg == co::TDN::HYPERLINK_CLICKED {
|
||||
extern "system" fn task_dialog_callback(_hwnd: HWND, msg: TASKDIALOG_NOTIFICATIONS, _: WPARAM, _: LPARAM, lp_ref_data: isize) -> HRESULT {
|
||||
if msg == TDN_HYPERLINK_CLICKED {
|
||||
let raw = lp_ref_data as *const PathBuf;
|
||||
let path: &PathBuf = unsafe { &*raw };
|
||||
let dir = path.to_str().unwrap();
|
||||
w::HWND::GetDesktopWindow().ShellExecute("open", &dir, None, None, co::SW::SHOWDEFAULT).ok();
|
||||
return co::HRESULT::S_FALSE; // do not close dialog
|
||||
let dir = path.to_string_lossy().to_string();
|
||||
let dir = string_to_wide(dir);
|
||||
unsafe { ShellExecuteW(Some(GetDesktopWindow()), None, dir.as_pcwstr(), None, None, SW_SHOWDEFAULT) };
|
||||
return S_FALSE; // do not close dialog
|
||||
}
|
||||
return co::HRESULT::S_OK; // close dialog on button press
|
||||
return S_OK; // close dialog on button press
|
||||
}
|
||||
|
||||
pub fn generate_confirm(
|
||||
@@ -217,42 +223,40 @@ pub fn generate_confirm(
|
||||
btns: DialogButton,
|
||||
ico: DialogIcon,
|
||||
) -> Result<DialogResult> {
|
||||
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
|
||||
let hparent = unsafe { GetDesktopWindow() };
|
||||
let mut ok_text_buf = ok_text.map(string_to_wide);
|
||||
let mut custom_btns = if let Some(ok_text_buf) = ok_text_buf.as_mut() {
|
||||
let td_btn = TASKDIALOG_BUTTON { nButtonID: IDOK.0, pszButtonText: ok_text_buf.as_pcwstr() };
|
||||
vec![td_btn]
|
||||
} else {
|
||||
Vec::<w::TASKDIALOG_BUTTON>::default()
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
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;
|
||||
let mut tdc = TASKDIALOGCONFIG { hwndParent: hparent, ..Default::default() };
|
||||
tdc.cbSize = std::mem::size_of::<TASKDIALOGCONFIG>() as u32;
|
||||
tdc.dwFlags = TDF_ALLOW_DIALOG_CANCELLATION | TDF_POSITION_RELATIVE_TO_WINDOW;
|
||||
tdc.dwCommonButtons = btns.to_win();
|
||||
tdc.set_pszMainIcon(w::IconIdTdicon::Tdicon(ico.to_win()));
|
||||
tdc.Anonymous1.pszMainIcon = ico.to_win();
|
||||
|
||||
if ok_text.is_some() {
|
||||
tdc.set_pButtons(Some(&mut custom_btns));
|
||||
if !custom_btns.is_empty() {
|
||||
tdc.cButtons = custom_btns.len() as u32;
|
||||
tdc.pButtons = custom_btns.as_mut_ptr();
|
||||
}
|
||||
|
||||
let mut title_buf = WString::from_str(title);
|
||||
tdc.set_pszWindowTitle(Some(&mut title_buf));
|
||||
let title_buf = string_to_wide(title);
|
||||
tdc.pszWindowTitle = title_buf.as_pcwstr();
|
||||
|
||||
let mut header_buf = WString::from_opt_str(header);
|
||||
if header.is_some() {
|
||||
tdc.set_pszMainInstruction(Some(&mut header_buf));
|
||||
let mut header_buf = header.map(string_to_wide);
|
||||
if let Some(header_buf) = header_buf.as_mut() {
|
||||
tdc.pszMainInstruction = header_buf.as_pcwstr();
|
||||
}
|
||||
|
||||
let mut body_buf = WString::from_str(body);
|
||||
tdc.set_pszContent(Some(&mut body_buf));
|
||||
let body_buf = string_to_wide(body);
|
||||
tdc.pszContent = body_buf.as_pcwstr();
|
||||
|
||||
let result = w::TaskDialogIndirect(&tdc, None).map(|(dlg_id, _)| dlg_id)?;
|
||||
Ok(DialogResult::from_win(result))
|
||||
let mut pnbutton = 0;
|
||||
unsafe { TaskDialogIndirect(&tdc, Some(&mut pnbutton), None, None).expect("didnt work") };
|
||||
Ok(DialogResult::from_win(pnbutton))
|
||||
}
|
||||
|
||||
pub fn generate_alert(
|
||||
@@ -263,6 +267,28 @@ pub fn generate_alert(
|
||||
btns: DialogButton,
|
||||
ico: DialogIcon,
|
||||
) -> Result<()> {
|
||||
let _ = generate_confirm(title, header, body, ok_text, btns, ico).map(|_| ())?;
|
||||
let _ = generate_confirm(title, header, body, ok_text, btns, ico)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn show_all_windows_dialogs() {
|
||||
use semver::Version;
|
||||
let app = Manifest {
|
||||
id: "test.app".to_string(),
|
||||
title: "Test Application".to_string(),
|
||||
version: semver::Version::new(1, 0, 0),
|
||||
description: "A test application for dialog generation.".to_string(),
|
||||
authors: "Test Author".to_string(),
|
||||
runtime_dependencies: "net8-x64".to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
show_restart_required(&app);
|
||||
show_update_missing_dependencies_dialog(&app, "net8-x64", &Version::new(1, 0, 0), &Version::new(2, 0, 0));
|
||||
show_setup_missing_dependencies_dialog(&app, "net8-x64");
|
||||
show_uninstall_complete_with_errors_dialog("Test Application", Some(&PathBuf::from("C:\\audio.log")));
|
||||
show_processes_locking_folder_dialog(&app.title, &app.version.to_string(), "TestProcess1, TestProcess2");
|
||||
show_overwrite_repair_dialog(&app, &PathBuf::from("C:\\Program Files\\TestApp"), false);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ pub mod prerequisite;
|
||||
pub mod runtimes;
|
||||
pub mod splash;
|
||||
pub mod known_path;
|
||||
|
||||
pub mod strings;
|
||||
pub mod registry;
|
||||
pub mod webview2;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use anyhow::Result;
|
||||
use chrono::{Datelike, Local as DateTime};
|
||||
use velopack::locator::VelopackLocator;
|
||||
use winsafe::{self as w, co, prelude::*};
|
||||
use winreg::{enums::*, RegKey};
|
||||
|
||||
const UNINSTALL_REGISTRY_KEY: &'static str = "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall";
|
||||
|
||||
@@ -17,6 +17,7 @@ pub fn write_uninstall_entry(locator: &VelopackLocator) -> Result<()> {
|
||||
let updater_path = locator.get_update_path_as_string();
|
||||
|
||||
let folder_size = fs_extra::dir::get_size(locator.get_current_bin_dir()).unwrap_or(0);
|
||||
let folder_size_kb = folder_size / 1024;
|
||||
let short_version = locator.get_manifest_version_short_string();
|
||||
|
||||
let now = DateTime::now();
|
||||
@@ -25,29 +26,33 @@ pub fn write_uninstall_entry(locator: &VelopackLocator) -> Result<()> {
|
||||
let uninstall_cmd = format!("\"{}\" --uninstall", updater_path);
|
||||
let uninstall_quiet: String = format!("\"{}\" --uninstall --silent", updater_path);
|
||||
|
||||
let reg_uninstall =
|
||||
w::HKEY::CURRENT_USER.RegCreateKeyEx(UNINSTALL_REGISTRY_KEY, None, co::REG_OPTION::NoValue, co::KEY::CREATE_SUB_KEY, None)?.0;
|
||||
let reg_app = reg_uninstall.RegCreateKeyEx(&app_id, None, co::REG_OPTION::NoValue, co::KEY::ALL_ACCESS, None)?.0;
|
||||
reg_app.RegSetKeyValue(None, Some("DisplayIcon"), w::RegistryValue::Sz(main_exe_path))?;
|
||||
reg_app.RegSetKeyValue(None, Some("DisplayName"), w::RegistryValue::Sz(app_title))?;
|
||||
reg_app.RegSetKeyValue(None, Some("DisplayVersion"), w::RegistryValue::Sz(short_version))?;
|
||||
reg_app.RegSetKeyValue(None, Some("InstallDate"), w::RegistryValue::Sz(formatted_date))?;
|
||||
reg_app.RegSetKeyValue(None, Some("InstallLocation"), w::RegistryValue::Sz(root_path_str))?;
|
||||
reg_app.RegSetKeyValue(None, Some("Publisher"), w::RegistryValue::Sz(app_authors))?;
|
||||
reg_app.RegSetKeyValue(None, Some("QuietUninstallString"), w::RegistryValue::Sz(uninstall_quiet))?;
|
||||
reg_app.RegSetKeyValue(None, Some("UninstallString"), w::RegistryValue::Sz(uninstall_cmd))?;
|
||||
reg_app.RegSetKeyValue(None, Some("EstimatedSize"), w::RegistryValue::Dword((folder_size / 1024).try_into()?))?;
|
||||
reg_app.RegSetKeyValue(None, Some("NoModify"), w::RegistryValue::Dword(1))?;
|
||||
reg_app.RegSetKeyValue(None, Some("NoRepair"), w::RegistryValue::Dword(1))?;
|
||||
reg_app.RegSetKeyValue(None, Some("Language"), w::RegistryValue::Dword(0x0409))?;
|
||||
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
||||
let (reg_uninstall, _reg_uninstall_disp) = hkcu.create_subkey(UNINSTALL_REGISTRY_KEY)?;
|
||||
let (reg_app, _reg_app_disp) = reg_uninstall.create_subkey(&app_id)?;
|
||||
|
||||
let u32true = 1u32;
|
||||
let language = 0x0409u32;
|
||||
|
||||
reg_app.set_value("DisplayIcon", &main_exe_path)?;
|
||||
reg_app.set_value("DisplayName", &app_title)?;
|
||||
reg_app.set_value("DisplayVersion", &short_version)?;
|
||||
reg_app.set_value("InstallDate", &formatted_date)?;
|
||||
reg_app.set_value("InstallLocation", &root_path_str)?;
|
||||
reg_app.set_value("Publisher", &app_authors)?;
|
||||
reg_app.set_value("QuietUninstallString", &uninstall_quiet)?;
|
||||
reg_app.set_value("UninstallString", &uninstall_cmd)?;
|
||||
reg_app.set_value("EstimatedSize", &folder_size_kb)?;
|
||||
reg_app.set_value("NoModify", &u32true)?;
|
||||
reg_app.set_value("NoRepair", &u32true)?;
|
||||
reg_app.set_value("Language", &language)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove_uninstall_entry(locator: &VelopackLocator) -> Result<()> {
|
||||
info!("Removing uninstall registry keys...");
|
||||
let app_id = locator.get_manifest_id();
|
||||
let reg_uninstall =
|
||||
w::HKEY::CURRENT_USER.RegCreateKeyEx(UNINSTALL_REGISTRY_KEY, None, co::REG_OPTION::NoValue, co::KEY::CREATE_SUB_KEY, None)?.0;
|
||||
reg_uninstall.RegDeleteKey(&app_id)?;
|
||||
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
||||
let (reg_uninstall, _reg_uninstall_disp) = hkcu.create_subkey(UNINSTALL_REGISTRY_KEY)?;
|
||||
reg_uninstall.delete_subkey_all(&app_id)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -4,7 +4,7 @@ use regex::Regex;
|
||||
use std::process::Command as Process;
|
||||
use std::{collections::HashMap, fs, path::Path};
|
||||
use velopack::download;
|
||||
use winsafe::{self as w, co, prelude::*};
|
||||
use winreg::{enums::*, RegKey};
|
||||
|
||||
const REDIST_2015_2022_X86: &str = "https://aka.ms/vs/17/release/vc_redist.x86.exe";
|
||||
const REDIST_2015_2022_X64: &str = "https://aka.ms/vs/17/release/vc_redist.x64.exe";
|
||||
@@ -129,20 +129,17 @@ impl RuntimeInfo for FullFrameworkInfo {
|
||||
}
|
||||
|
||||
fn is_installed(&self) -> bool {
|
||||
let lm = w::HKEY::LOCAL_MACHINE;
|
||||
let key = lm.RegOpenKeyEx(Some(NDP_REG_KEY), co::REG_OPTION::NoValue, co::KEY::READ);
|
||||
if key.is_err() {
|
||||
// key doesn't exist, so .net framework not installed
|
||||
return false;
|
||||
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
|
||||
match hklm.open_subkey(NDP_REG_KEY) {
|
||||
Ok(ndp) => {
|
||||
let dword_val: std::io::Result<u32> = ndp.get_value("Release");
|
||||
if let Ok(rel) = dword_val {
|
||||
rel >= self.release_version
|
||||
} else {
|
||||
false // key doesn't exist, so .net framework not installed
|
||||
}
|
||||
let release = key.unwrap().RegGetValue(None, Some("Release"));
|
||||
if release.is_err() {
|
||||
// key doesn't exist, so .net framework not installed
|
||||
return false;
|
||||
}
|
||||
match release.unwrap() {
|
||||
w::RegistryValue::Dword(v) => return v >= self.release_version,
|
||||
_ => return false,
|
||||
Err(_) => false, // key doesn't exist, so .net framework not installed
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,8 +183,8 @@ impl RuntimeInfo for VCRedistInfo {
|
||||
|
||||
fn is_installed(&self) -> bool {
|
||||
let mut installed_programs = HashMap::new();
|
||||
get_installed_programs(&mut installed_programs, co::KEY::READ | co::KEY::WOW64_32KEY);
|
||||
get_installed_programs(&mut installed_programs, co::KEY::READ | co::KEY::WOW64_64KEY);
|
||||
get_installed_programs(&mut installed_programs, KEY_READ | KEY_WOW64_32KEY);
|
||||
get_installed_programs(&mut installed_programs, KEY_READ | KEY_WOW64_64KEY);
|
||||
let (my_major, my_minor, my_build, _) = util::parse_version(&self.min_version).unwrap();
|
||||
let reg = Regex::new(r"(?i)Microsoft Visual C\+\+(.*)Redistributable").unwrap();
|
||||
for (k, v) in installed_programs {
|
||||
@@ -212,26 +209,22 @@ impl RuntimeInfo for VCRedistInfo {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_installed_programs(map: &mut HashMap<String, String>, access_rights: co::KEY) {
|
||||
let key = w::HKEY::LOCAL_MACHINE.RegOpenKeyEx(Some(UNINSTALL_REG_KEY), co::REG_OPTION::NoValue, access_rights);
|
||||
fn get_installed_programs(map: &mut HashMap<String, String>, access_rights: u32) {
|
||||
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
|
||||
let key = hklm.open_subkey_with_flags(UNINSTALL_REG_KEY, access_rights);
|
||||
|
||||
if let Ok(view) = key {
|
||||
if let Ok(iter) = view.RegEnumKeyEx() {
|
||||
for key_result in iter {
|
||||
for key_result in view.enum_keys() {
|
||||
if let Ok(key_name) = key_result {
|
||||
let subkey = view.RegOpenKeyEx(Some(&key_name), co::REG_OPTION::NoValue, access_rights);
|
||||
let subkey = view.open_subkey_with_flags(key_name, access_rights);
|
||||
if subkey.is_err() {
|
||||
continue;
|
||||
}
|
||||
let subkey = subkey.unwrap();
|
||||
let name = subkey.RegQueryValueEx(Some("DisplayName"));
|
||||
let version = subkey.RegQueryValueEx(Some("DisplayVersion"));
|
||||
let name: std::io::Result<String> = subkey.get_value("DisplayName");
|
||||
let version: std::io::Result<String> = subkey.get_value("DisplayVersion");
|
||||
if name.is_ok() && version.is_ok() {
|
||||
if let w::RegistryValue::Sz(display_name) = name.unwrap() {
|
||||
if let w::RegistryValue::Sz(display_version) = version.unwrap() {
|
||||
map.insert(display_name, display_version);
|
||||
}
|
||||
}
|
||||
}
|
||||
map.insert(name.unwrap(), version.unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -241,7 +234,7 @@ fn get_installed_programs(map: &mut HashMap<String, String>, access_rights: co::
|
||||
#[test]
|
||||
fn test_get_installed_programs_returns_visual_studio() {
|
||||
let mut map = HashMap::new();
|
||||
get_installed_programs(&mut map, co::KEY::READ | co::KEY::WOW64_64KEY);
|
||||
get_installed_programs(&mut map, KEY_READ | KEY_WOW64_64KEY);
|
||||
assert!(map.contains_key("Microsoft Visual Studio Installer"));
|
||||
}
|
||||
|
||||
@@ -557,7 +550,11 @@ impl RuntimeInfo for WebView2Info {
|
||||
}
|
||||
|
||||
fn install(&self, installer_path: &str, quiet: bool) -> Result<RuntimeInstallResult> {
|
||||
let args = if quiet { vec!["/silent", "/install"] } else { vec!["/install"] };
|
||||
let args = if quiet {
|
||||
vec!["/silent", "/install"]
|
||||
} else {
|
||||
vec!["/install"]
|
||||
};
|
||||
|
||||
info!("Running installer: '{}', args={:?}", installer_path, args);
|
||||
let mut cmd = Process::new(installer_path).args(&args).spawn()?;
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
use super::strings::string_to_wide;
|
||||
use anyhow::{bail, Result};
|
||||
use image::{codecs::gif::GifDecoder, AnimationDecoder, DynamicImage, ImageFormat, ImageReader};
|
||||
use std::sync::atomic::{AtomicI16, Ordering};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
io::Cursor,
|
||||
ops::Deref,
|
||||
rc::Rc,
|
||||
sync::mpsc::{self, Receiver, Sender},
|
||||
thread,
|
||||
use std::sync::mpsc::{self, Receiver, Sender};
|
||||
use std::{io::Cursor, thread};
|
||||
use windows::{
|
||||
core::HRESULT,
|
||||
Win32::{
|
||||
Foundation::{COLORREF, HINSTANCE, HWND, LPARAM, LRESULT, POINT, RECT, S_OK, WPARAM},
|
||||
Graphics::Gdi::*,
|
||||
System::LibraryLoader::GetModuleHandleW,
|
||||
UI::{Controls::*, WindowsAndMessaging::*},
|
||||
},
|
||||
};
|
||||
use winsafe::guard::DeleteObjectGuard;
|
||||
use winsafe::{self as w, co, gui, prelude::*, WString};
|
||||
|
||||
const TMR_GIF: usize = 1;
|
||||
const MSG_NOMESSAGE: i16 = -99;
|
||||
@@ -33,10 +34,9 @@ pub fn show_splash_dialog(app_name: String, imgstream: Option<Vec<u8>>) -> Sende
|
||||
thread::spawn(move || {
|
||||
info!("Showing splash screen immediately...");
|
||||
if imgstream.is_some() {
|
||||
let _ = SplashWindow::new(app_name, imgstream.unwrap(), rx).and_then(|w| {
|
||||
w.run()?;
|
||||
Ok(())
|
||||
});
|
||||
if let Err(e) = unsafe { SplashWindow::run(app_name, imgstream.unwrap(), rx) } {
|
||||
error!("Failed to show splash screen: {:?}", e);
|
||||
}
|
||||
} else {
|
||||
let setup_name = format!("{} Setup", app_name);
|
||||
let content = format!("Installing {}...", app_name);
|
||||
@@ -46,21 +46,19 @@ pub fn show_splash_dialog(app_name: String, imgstream: Option<Vec<u8>>) -> Sende
|
||||
tx
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SplashWindow {
|
||||
wnd: gui::WindowMain,
|
||||
frames: Rc<Vec<DeleteObjectGuard<w::HBITMAP>>>,
|
||||
rx: Rc<Receiver<i16>>,
|
||||
delay: u16,
|
||||
progress: Rc<RefCell<i16>>,
|
||||
frame_idx: Rc<RefCell<usize>>,
|
||||
w: u16,
|
||||
h: u16,
|
||||
struct SplashWindow {
|
||||
frames: Vec<HBITMAP>,
|
||||
rx: Receiver<i16>,
|
||||
progress: i16,
|
||||
frame_idx: usize,
|
||||
w: i32,
|
||||
h: i32,
|
||||
hdc_screen: HDC,
|
||||
}
|
||||
|
||||
fn average(numbers: &[u16]) -> u16 {
|
||||
let sum: u16 = numbers.iter().sum();
|
||||
let count = numbers.len() as u16;
|
||||
fn average(numbers: &[u32]) -> u32 {
|
||||
let sum: u32 = numbers.iter().sum();
|
||||
let count = numbers.len() as u32;
|
||||
sum / count
|
||||
}
|
||||
|
||||
@@ -73,17 +71,30 @@ fn convert_rgba_to_bgra(image_data: &mut Vec<u8>) {
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "system" fn window_proc(hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
|
||||
let ptr = GetWindowLongPtrW(hwnd, GWL_USERDATA) as *mut SplashWindow;
|
||||
match ptr.as_mut() {
|
||||
Some(data) => LRESULT(data.handle_event(hwnd, msg, wparam, lparam)),
|
||||
// If the pointer is null, we can just call the default window procedure
|
||||
None => DefWindowProcW(hwnd, msg, wparam, lparam),
|
||||
}
|
||||
}
|
||||
|
||||
fn rgb(red: u8, green: u8, blue: u8) -> COLORREF {
|
||||
COLORREF((red as u32) | ((green as u32) << 8) | ((blue as u32) << 16))
|
||||
}
|
||||
|
||||
impl SplashWindow {
|
||||
pub fn new(app_name: String, img_stream: Vec<u8>, rx: Receiver<i16>) -> Result<Self> {
|
||||
pub unsafe fn run(app_name: String, img_stream: Vec<u8>, rx: Receiver<i16>) -> Result<()> {
|
||||
let mut delays = Vec::new();
|
||||
let mut frames = Vec::new();
|
||||
|
||||
let fmt_cursor = Cursor::new(&img_stream);
|
||||
let fmt_reader = ImageReader::new(fmt_cursor).with_guessed_format()?;
|
||||
let fmt = fmt_reader.format();
|
||||
|
||||
let dims = &fmt_reader.into_dimensions()?;
|
||||
let w: u16 = u16::try_from(dims.0)?;
|
||||
let h: u16 = u16::try_from(dims.1)?;
|
||||
let w: i32 = i32::try_from(dims.0)?;
|
||||
let h: i32 = i32::try_from(dims.1)?;
|
||||
|
||||
if Some(ImageFormat::Gif) == fmt {
|
||||
info!("Image is animated GIF ({}x{}), loading frames...", w, h);
|
||||
@@ -93,22 +104,22 @@ impl SplashWindow {
|
||||
for frame in dec_frames.into_iter() {
|
||||
let frame = frame?;
|
||||
let (num, dem) = frame.delay().numer_denom_ms();
|
||||
delays.push((num / dem) as u16);
|
||||
delays.push((num / dem) as u32);
|
||||
let dynamic = DynamicImage::from(frame.buffer().to_owned());
|
||||
let mut vec = dynamic.to_rgba8().to_vec();
|
||||
convert_rgba_to_bgra(&mut vec);
|
||||
let bitmap = w::HBITMAP::CreateBitmap(winsafe::SIZE { cx: w.into(), cy: h.into() }, 1, 32, vec.as_mut_ptr() as *mut u8)?;
|
||||
let bitmap = CreateBitmap(w, h, 1, 32, Some(vec.as_mut_ptr() as *mut _));
|
||||
frames.push(bitmap);
|
||||
}
|
||||
info!("Successfully loaded {} frames.", frames.len());
|
||||
} else {
|
||||
info!("Loading static image (detected {:?})...", fmt);
|
||||
delays.push(16); // 60 fps
|
||||
delays.push(16u32); // 60 fps
|
||||
let img_cursor = Cursor::new(&img_stream);
|
||||
let img_decoder = ImageReader::new(img_cursor).with_guessed_format()?.decode()?;
|
||||
let mut vec = img_decoder.to_rgba8().to_vec();
|
||||
convert_rgba_to_bgra(&mut vec);
|
||||
let bitmap = w::HBITMAP::CreateBitmap(winsafe::SIZE { cx: w.into(), cy: h.into() }, 1, 32, vec.as_mut_ptr() as *mut u8)?;
|
||||
let bitmap = CreateBitmap(w, h, 1, 32, Some(vec.as_mut_ptr() as *mut _));
|
||||
frames.push(bitmap);
|
||||
info!("Successfully loaded.");
|
||||
}
|
||||
@@ -117,192 +128,229 @@ impl SplashWindow {
|
||||
// support a variable frame delay in the future.
|
||||
let delay = average(&delays);
|
||||
|
||||
let wnd = gui::WindowMain::new(gui::WindowMainOpts {
|
||||
class_icon: gui::Icon::Idi(co::IDI::APPLICATION),
|
||||
class_cursor: gui::Cursor::Idc(co::IDC::APPSTARTING),
|
||||
class_style: co::CS::HREDRAW | co::CS::VREDRAW,
|
||||
class_name: "VelopackSetupSplashWindow".to_owned(),
|
||||
title: app_name,
|
||||
size: (w.into(), h.into()),
|
||||
ex_style: co::WS_EX::NoValue,
|
||||
style: co::WS::POPUP,
|
||||
let class_name = string_to_wide("VelopackSetupSplashWindow");
|
||||
let app_name = string_to_wide(&app_name);
|
||||
|
||||
let h_instance: HINSTANCE = GetModuleHandleW(None)?.into();
|
||||
|
||||
let wnd_class = WNDCLASSEXW {
|
||||
cbSize: std::mem::size_of::<WNDCLASSEXW>() as u32,
|
||||
style: CS_HREDRAW | CS_VREDRAW,
|
||||
lpfnWndProc: Some(window_proc),
|
||||
hInstance: h_instance,
|
||||
hCursor: LoadCursorW(None, IDC_APPSTARTING)?,
|
||||
hbrBackground: CreateSolidBrush(rgb(0, 0, 0)),
|
||||
lpszClassName: class_name.as_pcwstr(),
|
||||
..Default::default()
|
||||
});
|
||||
};
|
||||
|
||||
let frames = Rc::new(frames);
|
||||
let rx = Rc::new(rx);
|
||||
let progress = Rc::new(RefCell::new(0));
|
||||
let frame_idx = Rc::new(RefCell::new(0));
|
||||
let mut new_self = Self { wnd, frames, delay, frame_idx, w, h, rx, progress };
|
||||
new_self.events();
|
||||
Ok(new_self)
|
||||
let class_id = unsafe { RegisterClassExW(&wnd_class) };
|
||||
if class_id == 0 {
|
||||
// if class already registered we can ignore
|
||||
let err = std::io::Error::last_os_error();
|
||||
if err.raw_os_error() != Some(1410) {
|
||||
bail!("Failed to register window class: {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&self) -> Result<i32> {
|
||||
let res = self.wnd.run_main(Some(co::SW::SHOWNOACTIVATE));
|
||||
if res.is_err() {
|
||||
error!("Error Showing Splash Window: {:?}", res);
|
||||
bail!("Error Showing Splash Window: {:?}", res);
|
||||
// center the window on the screen containing the cursor
|
||||
let mut lppoint = POINT::default();
|
||||
GetCursorPos(&mut lppoint)?;
|
||||
|
||||
let h_monitor = MonitorFromPoint(lppoint, MONITOR_DEFAULTTONEAREST);
|
||||
let mut mi: MONITORINFO = Default::default();
|
||||
mi.cbSize = std::mem::size_of::<MONITORINFO>() as u32;
|
||||
if GetMonitorInfoW(h_monitor, &mut mi).as_bool() {
|
||||
// center the window in the monitor
|
||||
let rc_monitor = mi.rcMonitor;
|
||||
let left = (rc_monitor.left + rc_monitor.right - w) / 2;
|
||||
let top = (rc_monitor.top + rc_monitor.bottom - h) / 2;
|
||||
lppoint.x = left;
|
||||
lppoint.y = top;
|
||||
} else {
|
||||
info!("Splash Window Closed");
|
||||
}
|
||||
Ok(res.unwrap())
|
||||
// fallback to work area if monitor info is not available
|
||||
let mut rc_work_area: RECT = Default::default();
|
||||
SystemParametersInfoW(
|
||||
SPI_GETWORKAREA,
|
||||
0,
|
||||
Some(&mut rc_work_area as *mut RECT as *mut _),
|
||||
SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS(0),
|
||||
)?;
|
||||
lppoint.x = (rc_work_area.left + rc_work_area.right - w) / 2;
|
||||
lppoint.y = (rc_work_area.top + rc_work_area.bottom - h) / 2;
|
||||
}
|
||||
|
||||
fn events(&mut self) {
|
||||
let self2 = self.clone();
|
||||
self.wnd.on().wm_create(move |_m| {
|
||||
// will ask Windows to give us a WM_TIMER every `delay` milliseconds
|
||||
self2.wnd.hwnd().SetTimer(TMR_GIF, self2.delay.into(), None)?;
|
||||
Ok(0)
|
||||
});
|
||||
let hwnd = CreateWindowExW(
|
||||
WS_EX_TOOLWINDOW | WS_EX_TOPMOST | WS_EX_NOACTIVATE,
|
||||
class_name.as_pcwstr(),
|
||||
app_name.as_pcwstr(),
|
||||
WS_CLIPCHILDREN | WS_POPUP,
|
||||
lppoint.x,
|
||||
lppoint.y,
|
||||
w,
|
||||
h,
|
||||
None,
|
||||
None,
|
||||
Some(h_instance),
|
||||
None,
|
||||
)?;
|
||||
|
||||
self.wnd.on().wm_nc_hit_test(|_m| {
|
||||
Ok(co::HT::CAPTION) // make the window draggable
|
||||
});
|
||||
let desktop = unsafe { GetDesktopWindow() };
|
||||
let hdc_screen = unsafe { GetDC(Some(desktop)) };
|
||||
|
||||
let self2 = self.clone();
|
||||
self.wnd.on().wm_timer(TMR_GIF, move || {
|
||||
// handle any incoming messages before painting
|
||||
loop {
|
||||
let msg = self2.rx.try_recv().unwrap_or(MSG_NOMESSAGE);
|
||||
if msg == MSG_NOMESSAGE {
|
||||
let data_ptr = Box::into_raw(Box::new(Self { frames, rx, frame_idx: 0, w, h, progress: 0, hdc_screen }));
|
||||
|
||||
SetWindowLongPtrW(hwnd, GWL_USERDATA, data_ptr as isize);
|
||||
let _ = ShowWindow(hwnd, SW_SHOWNOACTIVATE);
|
||||
SetTimer(Some(hwnd), TMR_GIF, delay, None);
|
||||
|
||||
let mut msg = MSG::default();
|
||||
let _ = PeekMessageW(&mut msg, Some(hwnd), 0, 0, PEEK_MESSAGE_REMOVE_TYPE(0)); // invoke creating message queue
|
||||
|
||||
while GetMessageW(&mut msg, None, 0, 0).as_bool() {
|
||||
if msg.message == WM_QUIT {
|
||||
break;
|
||||
} else if msg == MSG_CLOSE {
|
||||
self2.wnd.hwnd().SendMessage(w::msg::wm::Close {});
|
||||
return Ok(());
|
||||
} else if msg >= 0 {
|
||||
let mut p = self2.progress.borrow_mut();
|
||||
*p = msg;
|
||||
}
|
||||
let _ = TranslateMessage(&msg);
|
||||
DispatchMessageW(&msg);
|
||||
}
|
||||
|
||||
let arc_data = Box::from_raw(data_ptr); // drop the reference to the data
|
||||
let _ = DeleteDC(arc_data.hdc_screen);
|
||||
for h_bitmap in &arc_data.frames {
|
||||
let _ = DeleteObject((*h_bitmap).into());
|
||||
}
|
||||
|
||||
let _ = DestroyWindow(hwnd);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub unsafe fn handle_event(&mut self, hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> isize {
|
||||
match msg {
|
||||
WM_NCHITTEST => {
|
||||
return HTCAPTION as isize; // make the window draggable
|
||||
}
|
||||
WM_TIMER => {
|
||||
if wparam.0 as usize == TMR_GIF {
|
||||
// handle any incoming messages before painting
|
||||
let next_message = drain_and_get_next_message(&self.rx);
|
||||
if next_message == MSG_CLOSE {
|
||||
let _ = PostMessageW(Some(hwnd), WM_CLOSE, WPARAM(0), LPARAM(0));
|
||||
return 0;
|
||||
} else if next_message >= 0 {
|
||||
self.progress = next_message;
|
||||
}
|
||||
|
||||
// advance the frame index
|
||||
self.frame_idx += 1;
|
||||
if self.frame_idx >= self.frames.len() {
|
||||
self.frame_idx = 0; // loop back to the first frame
|
||||
}
|
||||
|
||||
// trigger a new WM_PAINT
|
||||
self2.wnd.hwnd().InvalidateRect(None, false)?;
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let self2 = self.clone();
|
||||
self.wnd.on().wm_paint(move || {
|
||||
// initial setup
|
||||
let hwnd = self2.wnd.hwnd();
|
||||
let rect = hwnd.GetClientRect()?;
|
||||
let hdc = hwnd.BeginPaint()?;
|
||||
let w = rect.right - rect.left;
|
||||
let h = rect.bottom - rect.top;
|
||||
let desktop = w::HWND::GetDesktopWindow();
|
||||
let hdc_screen = desktop.GetDC()?;
|
||||
|
||||
// retrieve the next frame to draw
|
||||
let mut idx = self2.frame_idx.borrow_mut();
|
||||
let h_bitmap = self2.frames[*idx].deref();
|
||||
*idx += 1;
|
||||
if *idx >= self2.frames.len() {
|
||||
*idx = 0;
|
||||
let _ = InvalidateRect(Some(hwnd), None, false);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
WM_PAINT => {
|
||||
let mut ps = PAINTSTRUCT::default();
|
||||
let hdc = BeginPaint(hwnd, &mut ps);
|
||||
if hdc.is_invalid() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// get the bitmap for the current frame
|
||||
let h_bitmap = self.frames[self.frame_idx];
|
||||
|
||||
// create double buffer
|
||||
let hdc_mem = hdc_screen.CreateCompatibleDC()?;
|
||||
let buffer_bmp = hdc_screen.CreateCompatibleBitmap(w, h)?;
|
||||
let _buffer_old = hdc_mem.SelectObject(buffer_bmp.deref())?;
|
||||
let hdc_mem = CreateCompatibleDC(Some(self.hdc_screen));
|
||||
let buffer_bmp = CreateCompatibleBitmap(self.hdc_screen, self.w, self.h);
|
||||
let buffer_old = SelectObject(hdc_mem, buffer_bmp.into());
|
||||
|
||||
// load image into hdc_bitmap
|
||||
let hdc_bitmap = hdc_screen.CreateCompatibleDC()?;
|
||||
let _bitmap_old = hdc_bitmap.SelectObject(h_bitmap)?;
|
||||
let hdc_bitmap = CreateCompatibleDC(Some(self.hdc_screen));
|
||||
let bitmap_old = SelectObject(hdc_bitmap, h_bitmap.into());
|
||||
|
||||
// draw background to hdc_mem
|
||||
let background_brush = w::HBRUSH::CreateSolidBrush(w::COLORREF::new(0, 0, 0))?;
|
||||
hdc_mem.FillRect(w::RECT { left: 0, top: 0, right: w, bottom: h }, &background_brush)?;
|
||||
let background_brush = CreateSolidBrush(rgb(0, 0, 0));
|
||||
FillRect(hdc_mem, &RECT { left: 0, top: 0, right: self.w, bottom: self.h }, background_brush);
|
||||
|
||||
// copy bitmap from hdc_bitmap to hdc_mem
|
||||
hdc_mem.SetStretchBltMode(co::STRETCH_MODE::STRETCH_HALFTONE)?;
|
||||
hdc_mem.StretchBlt(
|
||||
w::POINT { x: 0, y: 0 },
|
||||
w::SIZE { cx: rect.right, cy: rect.bottom },
|
||||
&hdc_bitmap,
|
||||
w::POINT { x: 0, y: 0 },
|
||||
w::SIZE { cx: self2.w.into(), cy: self2.h.into() },
|
||||
co::ROP::SRCCOPY,
|
||||
)?;
|
||||
SetStretchBltMode(hdc_mem, STRETCH_HALFTONE);
|
||||
let _ = StretchBlt(hdc_mem, 0, 0, self.w, self.h, Some(hdc_bitmap), 0, 0, self.w, self.h, SRCCOPY);
|
||||
|
||||
// draw progress bar to hdc_mem
|
||||
let progress = self2.progress.borrow();
|
||||
let progress_brush = w::HBRUSH::CreateSolidBrush(w::COLORREF::new(0, 255, 0))?;
|
||||
let progress_width = (rect.right as f32 * (*progress as f32 / 100.0)) as i32;
|
||||
let progress_rect = w::RECT { left: 0, bottom: rect.bottom, right: progress_width, top: rect.bottom - 10 };
|
||||
hdc_mem.FillRect(progress_rect, &progress_brush)?;
|
||||
let progress = self.progress;
|
||||
let progress_brush = CreateSolidBrush(rgb(15, 123, 15));
|
||||
let progress_width = (self.w as f32 * (progress as f32 / 100.0)) as i32;
|
||||
let progress_rect = RECT { left: 0, bottom: self.h, right: progress_width, top: self.h - 10 };
|
||||
FillRect(hdc_mem, &progress_rect, progress_brush);
|
||||
|
||||
// finally, copy hdc_mem to hdc
|
||||
hdc.BitBlt(w::POINT { x: 0, y: 0 }, w::SIZE { cx: w, cy: h }, &hdc_mem, w::POINT { x: 0, y: 0 }, co::ROP::SRCCOPY)?;
|
||||
let _ = BitBlt(hdc, 0, 0, self.w, self.h, Some(hdc_mem), 0, 0, SRCCOPY);
|
||||
|
||||
Ok(())
|
||||
});
|
||||
// clean up
|
||||
let _ = DeleteObject(background_brush.into());
|
||||
let _ = DeleteObject(progress_brush.into());
|
||||
SelectObject(hdc_mem, buffer_old);
|
||||
SelectObject(hdc_bitmap, bitmap_old);
|
||||
let _ = DeleteDC(hdc_mem);
|
||||
let _ = DeleteDC(hdc_bitmap);
|
||||
let _ = DeleteObject(buffer_bmp.into());
|
||||
|
||||
let _ = EndPaint(hwnd, &ps);
|
||||
return 0;
|
||||
}
|
||||
_ => {
|
||||
// handle other messages
|
||||
return DefWindowProcW(hwnd, msg, wparam, lparam).0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const TDM_SET_PROGRESS_BAR_MARQUEE: co::WM = unsafe { co::WM::from_raw(1131) };
|
||||
pub const TDM_SET_MARQUEE_PROGRESS_BAR: co::WM = unsafe { co::WM::from_raw(1127) };
|
||||
pub const TDM_SET_PROGRESS_BAR_POS: co::WM = unsafe { co::WM::from_raw(1130) };
|
||||
|
||||
struct MsgSetProgressMarqueeOnOff {
|
||||
is_marquee_on: bool,
|
||||
}
|
||||
unsafe impl MsgSend for MsgSetProgressMarqueeOnOff {
|
||||
type RetType = ();
|
||||
fn convert_ret(&self, _: isize) -> Self::RetType {
|
||||
()
|
||||
}
|
||||
fn as_generic_wm(&mut self) -> w::msg::WndMsg {
|
||||
let v: usize = if self.is_marquee_on { 1 } else { 0 };
|
||||
w::msg::WndMsg { msg_id: TDM_SET_PROGRESS_BAR_MARQUEE, wparam: v, lparam: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
struct MsgSetProgressMarqueeMode {
|
||||
is_marquee_on: bool,
|
||||
}
|
||||
unsafe impl MsgSend for MsgSetProgressMarqueeMode {
|
||||
type RetType = ();
|
||||
fn convert_ret(&self, _: isize) -> Self::RetType {
|
||||
()
|
||||
}
|
||||
fn as_generic_wm(&mut self) -> w::msg::WndMsg {
|
||||
let v: usize = if self.is_marquee_on { 1 } else { 0 };
|
||||
w::msg::WndMsg { msg_id: TDM_SET_MARQUEE_PROGRESS_BAR, wparam: v, lparam: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
struct MsgSetProgressPos {
|
||||
pos: usize,
|
||||
}
|
||||
unsafe impl MsgSend for MsgSetProgressPos {
|
||||
type RetType = ();
|
||||
fn convert_ret(&self, _: isize) -> Self::RetType {
|
||||
()
|
||||
}
|
||||
fn as_generic_wm(&mut self) -> w::msg::WndMsg {
|
||||
w::msg::WndMsg { msg_id: TDM_SET_PROGRESS_BAR_POS, wparam: self.pos, lparam: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ComCtlProgressWindow {
|
||||
// hwnd: Rc<RefCell<w::HWND>>,
|
||||
rx: Rc<Receiver<i16>>,
|
||||
last_progress: Rc<AtomicI16>,
|
||||
rx: Receiver<i16>,
|
||||
last_progress: i16,
|
||||
}
|
||||
|
||||
impl ComCtlProgressWindow {
|
||||
pub fn set_progress(&self, value: i16) {
|
||||
self.last_progress.store(value, Ordering::SeqCst);
|
||||
fn show_com_ctl_progress_dialog(rx: Receiver<i16>, window_title: &str, content: &str) {
|
||||
let window_title = string_to_wide(window_title);
|
||||
let content = string_to_wide(content);
|
||||
let ok_text = string_to_wide("Hide");
|
||||
|
||||
let td_btn = TASKDIALOG_BUTTON {
|
||||
nButtonID: 1, // OK button id
|
||||
pszButtonText: ok_text.as_pcwstr(),
|
||||
};
|
||||
let custom_btns = vec![td_btn];
|
||||
|
||||
let mut config: TASKDIALOGCONFIG = Default::default();
|
||||
config.cbSize = std::mem::size_of::<TASKDIALOGCONFIG>() as u32;
|
||||
config.dwFlags = TDF_SIZE_TO_CONTENT | TDF_SHOW_PROGRESS_BAR | TDF_CALLBACK_TIMER;
|
||||
config.pszWindowTitle = window_title.as_pcwstr();
|
||||
config.pszMainInstruction = content.as_pcwstr();
|
||||
config.pButtons = custom_btns.as_ptr();
|
||||
config.cButtons = custom_btns.len() as u32;
|
||||
config.nDefaultButton = 1;
|
||||
config.Anonymous1.pszMainIcon = TD_INFORMATION_ICON;
|
||||
|
||||
let me = ComCtlProgressWindow { rx, last_progress: 0 };
|
||||
let data_ptr = Box::into_raw(Box::new(me));
|
||||
config.lpCallbackData = data_ptr as isize;
|
||||
config.pfCallback = Some(task_dialog_callback);
|
||||
|
||||
unsafe {
|
||||
let _ = TaskDialogIndirect(&config, None, None, None);
|
||||
}
|
||||
pub fn get_progress(&self) -> i16 {
|
||||
self.last_progress.load(Ordering::SeqCst)
|
||||
}
|
||||
pub fn get_next_message(&self) -> i16 {
|
||||
|
||||
let _ = unsafe { Box::from_raw(data_ptr) }; // This will drop the ComCtlProgressWindow instance
|
||||
}
|
||||
|
||||
fn drain_and_get_next_message(rx: &Receiver<i16>) -> i16 {
|
||||
let mut progress: i16 = MSG_NOMESSAGE;
|
||||
loop {
|
||||
let msg = self.rx.try_recv().unwrap_or(MSG_NOMESSAGE);
|
||||
let msg = rx.try_recv().unwrap_or(MSG_NOMESSAGE);
|
||||
if msg == MSG_NOMESSAGE {
|
||||
break;
|
||||
} else {
|
||||
@@ -310,63 +358,38 @@ impl ComCtlProgressWindow {
|
||||
}
|
||||
}
|
||||
progress
|
||||
}
|
||||
}
|
||||
|
||||
fn show_com_ctl_progress_dialog(rx: Receiver<i16>, window_title: &str, content: &str) {
|
||||
let mut window_title = WString::from_str(window_title);
|
||||
let mut content = WString::from_str(content);
|
||||
unsafe extern "system" fn task_dialog_callback(
|
||||
hwnd: HWND,
|
||||
msg: TASKDIALOG_NOTIFICATIONS,
|
||||
_wparam: WPARAM,
|
||||
_lparam: LPARAM,
|
||||
lp_ref_data: isize,
|
||||
) -> HRESULT {
|
||||
let raw = lp_ref_data as *mut ComCtlProgressWindow;
|
||||
|
||||
let mut ok_text_buf = WString::from_str("Hide");
|
||||
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);
|
||||
|
||||
let mut config: w::TASKDIALOGCONFIG = Default::default();
|
||||
config.dwFlags = co::TDF::SIZE_TO_CONTENT | co::TDF::SHOW_PROGRESS_BAR | co::TDF::CALLBACK_TIMER;
|
||||
config.set_pszMainIcon(w::IconIdTdicon::Tdicon(co::TD_ICON::INFORMATION));
|
||||
config.set_pszWindowTitle(Some(&mut window_title));
|
||||
config.set_pszMainInstruction(Some(&mut content));
|
||||
config.set_pButtons(Some(&mut custom_btns));
|
||||
|
||||
// if (_icon != null) {
|
||||
// config.dwFlags |= TASKDIALOG_FLAGS.TDF_USE_HICON_MAIN;
|
||||
// config.mainIcon = _icon.Handle;
|
||||
// }
|
||||
|
||||
let me = ComCtlProgressWindow { rx: Rc::new(rx), last_progress: Rc::new(AtomicI16::new(0)) };
|
||||
config.lpCallbackData = &me as *const ComCtlProgressWindow as usize;
|
||||
config.pfCallback = Some(task_dialog_callback);
|
||||
|
||||
let _ = w::TaskDialogIndirect(&config, None);
|
||||
}
|
||||
|
||||
extern "system" fn task_dialog_callback(hwnd: w::HWND, msg: co::TDN, _: usize, _: isize, lp_ref_data: usize) -> co::HRESULT {
|
||||
let raw = lp_ref_data as *const ComCtlProgressWindow;
|
||||
let me: &ComCtlProgressWindow = unsafe { &*raw };
|
||||
|
||||
if msg == co::TDN::TIMER {
|
||||
let next_message = me.get_next_message();
|
||||
if let Some(me) = raw.as_mut() {
|
||||
if msg == TDN_TIMER {
|
||||
let next_message = drain_and_get_next_message(&me.rx);
|
||||
if next_message == MSG_CLOSE {
|
||||
let _ = hwnd.EndDialog(0);
|
||||
return co::HRESULT::S_OK;
|
||||
let _ = EndDialog(hwnd, 0);
|
||||
} else if next_message == MSG_INDEFINITE {
|
||||
hwnd.SendMessage(MsgSetProgressMarqueeOnOff { is_marquee_on: true });
|
||||
hwnd.SendMessage(MsgSetProgressMarqueeMode { is_marquee_on: true });
|
||||
me.set_progress(MSG_INDEFINITE);
|
||||
SendMessageW(hwnd, TDM_SET_PROGRESS_BAR_MARQUEE.0 as u32, Some(WPARAM(1)), Some(LPARAM(0)));
|
||||
SendMessageW(hwnd, TDM_SET_MARQUEE_PROGRESS_BAR.0 as u32, Some(WPARAM(1)), Some(LPARAM(0)));
|
||||
me.last_progress = MSG_INDEFINITE;
|
||||
} else if next_message >= 0 {
|
||||
if me.get_progress() < 0 {
|
||||
hwnd.SendMessage(MsgSetProgressMarqueeOnOff { is_marquee_on: false });
|
||||
hwnd.SendMessage(MsgSetProgressMarqueeMode { is_marquee_on: false });
|
||||
if me.last_progress < 0 {
|
||||
SendMessageW(hwnd, TDM_SET_PROGRESS_BAR_MARQUEE.0 as u32, Some(WPARAM(0)), Some(LPARAM(0)));
|
||||
SendMessageW(hwnd, TDM_SET_MARQUEE_PROGRESS_BAR.0 as u32, Some(WPARAM(0)), Some(LPARAM(0)));
|
||||
}
|
||||
SendMessageW(hwnd, TDM_SET_PROGRESS_BAR_POS.0 as u32, Some(WPARAM(next_message as usize)), Some(LPARAM(0)));
|
||||
me.last_progress = next_message;
|
||||
}
|
||||
hwnd.SendMessage(MsgSetProgressPos { pos: next_message as usize });
|
||||
me.set_progress(next_message);
|
||||
}
|
||||
}
|
||||
|
||||
return co::HRESULT::S_OK;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -374,8 +397,16 @@ extern "system" fn task_dialog_callback(hwnd: w::HWND, msg: co::TDN, _: usize, _
|
||||
fn show_test_gif() {
|
||||
let rd = std::fs::read(r"C:\Source\Clowd\artwork\splash.gif").unwrap();
|
||||
let tx = show_splash_dialog("osu!".to_string(), Some(rd));
|
||||
tx.send(80).unwrap();
|
||||
std::thread::sleep(std::time::Duration::from_secs(6));
|
||||
let _ = tx.send(25);
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
let _ = tx.send(50);
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
let _ = tx.send(75);
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
let _ = tx.send(100);
|
||||
std::thread::sleep(std::time::Duration::from_secs(3));
|
||||
let _ = tx.send(MSG_CLOSE);
|
||||
std::thread::sleep(std::time::Duration::from_secs(3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,34 +1,88 @@
|
||||
use anyhow::Result;
|
||||
use windows::core::{PCWSTR, PWSTR};
|
||||
|
||||
pub struct WideString {
|
||||
inner: Vec<u16>,
|
||||
}
|
||||
|
||||
impl WideString {
|
||||
pub fn as_ptr(&self) -> *const u16 {
|
||||
self.inner.as_ptr()
|
||||
}
|
||||
|
||||
pub fn as_mut_ptr(&mut self) -> *mut u16 {
|
||||
self.inner.as_mut_ptr()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.inner.len()
|
||||
}
|
||||
|
||||
pub fn as_pcwstr(&self) -> PCWSTR {
|
||||
PCWSTR(self.as_ptr())
|
||||
}
|
||||
|
||||
pub fn as_pwstr(&mut self) -> PWSTR {
|
||||
PWSTR(self.as_mut_ptr())
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Vec<u16>> for WideString {
|
||||
fn into(self) -> Vec<u16> {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<PCWSTR> for WideString {
|
||||
fn into(self) -> PCWSTR {
|
||||
self.as_pcwstr()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u16]> for WideString {
|
||||
fn as_ref(&self) -> &[u16] {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<[u16]> for WideString {
|
||||
fn as_mut(&mut self) -> &mut [u16] {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
pub fn string_to_u16<P: AsRef<str>>(input: P) -> Vec<u16> {
|
||||
let input = input.as_ref();
|
||||
input.encode_utf16().chain(Some(0)).collect::<Vec<u16>>()
|
||||
}
|
||||
|
||||
pub trait WideString {
|
||||
pub fn string_to_wide<P: AsRef<str>>(input: P) -> WideString {
|
||||
WideString { inner: string_to_u16(input) }
|
||||
}
|
||||
|
||||
pub trait WideStringRef {
|
||||
fn to_wide_slice(&self) -> &[u16];
|
||||
}
|
||||
|
||||
impl WideString for PWSTR {
|
||||
impl WideStringRef for PWSTR {
|
||||
fn to_wide_slice(&self) -> &[u16] {
|
||||
unsafe { self.as_wide() }
|
||||
}
|
||||
}
|
||||
|
||||
impl WideString for PCWSTR {
|
||||
impl WideStringRef for PCWSTR {
|
||||
fn to_wide_slice(&self) -> &[u16] {
|
||||
unsafe { self.as_wide() }
|
||||
}
|
||||
}
|
||||
|
||||
impl WideString for Vec<u16> {
|
||||
impl WideStringRef for Vec<u16> {
|
||||
fn to_wide_slice(&self) -> &[u16] {
|
||||
self.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl WideString for &Vec<u16> {
|
||||
impl WideStringRef for &Vec<u16> {
|
||||
fn to_wide_slice(&self) -> &[u16] {
|
||||
self.as_ref()
|
||||
}
|
||||
@@ -40,26 +94,27 @@ impl WideString for &Vec<u16> {
|
||||
// }
|
||||
// }
|
||||
|
||||
impl<const N: usize> WideString for [u16; N] {
|
||||
impl<const N: usize> WideStringRef for [u16; N] {
|
||||
fn to_wide_slice(&self) -> &[u16] {
|
||||
self.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn u16_to_string_lossy<T: WideString>(input: T) -> String {
|
||||
pub fn u16_to_string_lossy<T: WideStringRef>(input: T) -> String {
|
||||
let slice = input.to_wide_slice();
|
||||
let null_pos = slice.iter().position(|&x| x == 0).unwrap_or(slice.len());
|
||||
let trimmed_slice = &slice[..null_pos];
|
||||
String::from_utf16_lossy(trimmed_slice)
|
||||
}
|
||||
|
||||
pub fn u16_to_string<T: WideString>(input: T) -> Result<String> {
|
||||
pub fn u16_to_string<T: WideStringRef>(input: T) -> Result<String> {
|
||||
let slice = input.to_wide_slice();
|
||||
let null_pos = slice.iter().position(|&x| x == 0).unwrap_or(slice.len());
|
||||
let trimmed_slice = &slice[..null_pos];
|
||||
Ok(String::from_utf16(trimmed_slice)?)
|
||||
}
|
||||
|
||||
|
||||
// pub fn pwstr_to_string(input: PWSTR) -> Result<String> {
|
||||
// unsafe {
|
||||
// let hstring = input.to_hstring();
|
||||
|
||||
@@ -5,16 +5,16 @@ use common::*;
|
||||
use std::hint::assert_unchecked;
|
||||
use std::{fs, path::Path, path::PathBuf};
|
||||
use tempfile::tempdir;
|
||||
use velopack_bins::*;
|
||||
|
||||
use velopack_bins::*;
|
||||
use velopack_bins::windows::known_path;
|
||||
use velopack::bundle::load_bundle_from_file;
|
||||
use velopack::locator::{auto_locate_app_manifest, LocationContext};
|
||||
#[cfg(target_os = "windows")]
|
||||
use winsafe::{self as w, co};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[test]
|
||||
pub fn test_install_apply_uninstall() {
|
||||
|
||||
dialogs::set_silent(true);
|
||||
|
||||
let fixtures = find_fixtures();
|
||||
@@ -22,10 +22,8 @@ pub fn test_install_apply_uninstall() {
|
||||
let app_id = "AvaloniaCrossPlat";
|
||||
let pkg_name = "AvaloniaCrossPlat-1.0.11-win-full.nupkg";
|
||||
|
||||
let start_menu = w::SHGetKnownFolderPath(&co::KNOWNFOLDERID::StartMenu, co::KF::DONT_UNEXPAND, None).unwrap();
|
||||
let start_menu = Path::new(&start_menu).join("Programs");
|
||||
let desktop = w::SHGetKnownFolderPath(&co::KNOWNFOLDERID::Desktop, co::KF::DONT_UNEXPAND, None).unwrap();
|
||||
let desktop = Path::new(&desktop);
|
||||
let start_menu = PathBuf::from(known_path::get_start_menu().unwrap());
|
||||
let desktop = PathBuf::from(known_path::get_user_desktop().unwrap());
|
||||
|
||||
let lnk_start_1 = start_menu.join(format!("{}.lnk", app_id));
|
||||
let lnk_desktop_1 = desktop.join(format!("{}.lnk", app_id));
|
||||
|
||||
Reference in New Issue
Block a user