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",
|
"groupName": "frozen",
|
||||||
"matchPackageNames": [
|
"matchPackageNames": [
|
||||||
"winsafe", // newer versions causes runtime errors in release builds
|
|
||||||
"System.CommandLine", // too many breaking changes too frequently
|
"System.CommandLine", // too many breaking changes too frequently
|
||||||
"xunit.runner.visualstudio", // 20-12-2024: broke tests (something about sn signing maybe?)
|
"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
|
"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",
|
"walkdir",
|
||||||
"webview2-com-sys",
|
"webview2-com-sys",
|
||||||
"windows",
|
"windows",
|
||||||
|
"winreg",
|
||||||
"winres",
|
"winres",
|
||||||
"winsafe",
|
|
||||||
"zip",
|
"zip",
|
||||||
"zstd",
|
"zstd",
|
||||||
]
|
]
|
||||||
@@ -2844,6 +2844,16 @@ dependencies = [
|
|||||||
"memchr",
|
"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]]
|
[[package]]
|
||||||
name = "winres"
|
name = "winres"
|
||||||
version = "0.1.12"
|
version = "0.1.12"
|
||||||
@@ -2853,12 +2863,6 @@ dependencies = [
|
|||||||
"toml 0.5.11",
|
"toml 0.5.11",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winsafe"
|
|
||||||
version = "0.0.20"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a40369220be405a294b88b13ccc3d916fac12423250b30092c8c4ea19001f7f1"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wit-bindgen-rt"
|
name = "wit-bindgen-rt"
|
||||||
version = "0.39.0"
|
version = "0.39.0"
|
||||||
|
|||||||
@@ -77,7 +77,6 @@ fs_extra = "1.3"
|
|||||||
memmap2 = "0.9"
|
memmap2 = "0.9"
|
||||||
windows = "0.61"
|
windows = "0.61"
|
||||||
webview2-com-sys = "0.37"
|
webview2-com-sys = "0.37"
|
||||||
winsafe = { version = "0.0.20", features = ["gui"] }
|
|
||||||
cbindgen = "0.28"
|
cbindgen = "0.28"
|
||||||
log-panics = "2.1.0"
|
log-panics = "2.1.0"
|
||||||
core-foundation = "0.10"
|
core-foundation = "0.10"
|
||||||
@@ -87,9 +86,7 @@ walkdir = "2.5"
|
|||||||
rayon = "1.6"
|
rayon = "1.6"
|
||||||
progress-streams = "1.1"
|
progress-streams = "1.1"
|
||||||
flate2 = { version = "1.0", default-features = false }
|
flate2 = { version = "1.0", default-features = false }
|
||||||
# mtzip = "=4.0.2"
|
winreg = "0.55"
|
||||||
# ripunzip = "=2.0.1"
|
|
||||||
# zerofrom = "=0.1.5"
|
|
||||||
|
|
||||||
# default to small, optimized workspace release binaries
|
# default to small, optimized workspace release binaries
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
|||||||
@@ -85,7 +85,6 @@ waitpid-any.workspace = true
|
|||||||
fs_extra.workspace = true
|
fs_extra.workspace = true
|
||||||
memmap2.workspace = true
|
memmap2.workspace = true
|
||||||
image.workspace = true
|
image.workspace = true
|
||||||
winsafe.workspace = true
|
|
||||||
windows = { workspace = true, features = [
|
windows = { workspace = true, features = [
|
||||||
"Win32_Foundation",
|
"Win32_Foundation",
|
||||||
"Win32_Security",
|
"Win32_Security",
|
||||||
@@ -93,23 +92,26 @@ windows = { workspace = true, features = [
|
|||||||
"Win32_Globalization",
|
"Win32_Globalization",
|
||||||
"Win32_UI",
|
"Win32_UI",
|
||||||
"Win32_UI_Shell",
|
"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_Threading",
|
||||||
"Win32_System_SystemInformation",
|
"Win32_System_SystemInformation",
|
||||||
"Win32_System_Variant",
|
"Win32_System_Variant",
|
||||||
"Win32_System_Environment",
|
"Win32_System_Environment",
|
||||||
"Win32_Storage_EnhancedStorage",
|
|
||||||
"Win32_Storage_FileSystem",
|
|
||||||
"Win32_System_Com_StructuredStorage",
|
"Win32_System_Com_StructuredStorage",
|
||||||
"Win32_System_Registry",
|
"Win32_System_Registry",
|
||||||
"Win32_System_Threading",
|
"Win32_System_Threading",
|
||||||
"Win32_System_ProcessStatus",
|
"Win32_System_ProcessStatus",
|
||||||
"Win32_System_WindowsProgramming",
|
"Win32_System_WindowsProgramming",
|
||||||
"Win32_System_LibraryLoader",
|
"Win32_System_LibraryLoader",
|
||||||
"Win32_UI_Shell_Common",
|
|
||||||
"Win32_UI_Shell_PropertiesSystem",
|
|
||||||
"Win32_UI_WindowsAndMessaging",
|
|
||||||
"Win32_System_ApplicationInstallationAndServicing",
|
"Win32_System_ApplicationInstallationAndServicing",
|
||||||
"Win32_System_Kernel",
|
"Win32_System_Kernel",
|
||||||
|
"Win32_Storage_EnhancedStorage",
|
||||||
|
"Win32_Storage_FileSystem",
|
||||||
"Wdk",
|
"Wdk",
|
||||||
"Wdk_System",
|
"Wdk_System",
|
||||||
"Wdk_System_Threading",
|
"Wdk_System_Threading",
|
||||||
@@ -118,6 +120,7 @@ webview2-com-sys.workspace = true
|
|||||||
libloading.workspace = true
|
libloading.workspace = true
|
||||||
strsim.workspace = true
|
strsim.workspace = true
|
||||||
same-file.workspace = true
|
same-file.workspace = true
|
||||||
|
winreg.workspace = true
|
||||||
# filelocksmith.workspace = true
|
# filelocksmith.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ fn test_show_all_dialogs() {
|
|||||||
show_warn("Warning", None, "This is a warning.");
|
show_warn("Warning", None, "This is a warning.");
|
||||||
show_info("Information", None, "This is information.");
|
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.", 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> {
|
// pub fn yes_no(title: &str, header: Option<&str>, body: &str) -> Result<bool> {
|
||||||
|
|||||||
@@ -37,25 +37,26 @@ pub enum DialogResult {
|
|||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
impl DialogButton {
|
impl DialogButton {
|
||||||
pub fn to_win(&self) -> winsafe::co::TDCBF {
|
pub fn to_win(&self) -> windows::Win32::UI::Controls::TASKDIALOG_COMMON_BUTTON_FLAGS {
|
||||||
let mut result = unsafe { winsafe::co::TDCBF::from_raw(0) };
|
use windows::Win32::UI::Controls::*;
|
||||||
|
let mut result = TASKDIALOG_COMMON_BUTTON_FLAGS(0);
|
||||||
if self.has_ok() {
|
if self.has_ok() {
|
||||||
result |= winsafe::co::TDCBF::OK;
|
result |= TDCBF_OK_BUTTON;
|
||||||
}
|
}
|
||||||
if self.has_yes() {
|
if self.has_yes() {
|
||||||
result |= winsafe::co::TDCBF::YES;
|
result |= TDCBF_YES_BUTTON;
|
||||||
}
|
}
|
||||||
if self.has_no() {
|
if self.has_no() {
|
||||||
result |= winsafe::co::TDCBF::NO;
|
result |= TDCBF_NO_BUTTON;
|
||||||
}
|
}
|
||||||
if self.has_cancel() {
|
if self.has_cancel() {
|
||||||
result |= winsafe::co::TDCBF::CANCEL;
|
result |= TDCBF_CANCEL_BUTTON;
|
||||||
}
|
}
|
||||||
if self.has_retry() {
|
if self.has_retry() {
|
||||||
result |= winsafe::co::TDCBF::RETRY;
|
result |= TDCBF_RETRY_BUTTON;
|
||||||
}
|
}
|
||||||
if self.has_close() {
|
if self.has_close() {
|
||||||
result |= winsafe::co::TDCBF::CLOSE;
|
result |= TDCBF_CLOSE_BUTTON;
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
@@ -63,28 +64,31 @@ impl DialogButton {
|
|||||||
|
|
||||||
impl DialogIcon {
|
impl DialogIcon {
|
||||||
#[cfg(target_os = "windows")]
|
#[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 {
|
match self {
|
||||||
DialogIcon::Warning => winsafe::co::TD_ICON::WARNING,
|
DialogIcon::Warning => TD_WARNING_ICON,
|
||||||
DialogIcon::Error => winsafe::co::TD_ICON::ERROR,
|
DialogIcon::Error => TD_ERROR_ICON,
|
||||||
DialogIcon::Information => winsafe::co::TD_ICON::INFORMATION,
|
DialogIcon::Information => TD_INFORMATION_ICON,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
impl DialogResult {
|
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 {
|
match dlg_id {
|
||||||
winsafe::co::DLGID::OK => DialogResult::Ok,
|
IDOK => DialogResult::Ok,
|
||||||
winsafe::co::DLGID::CANCEL => DialogResult::Cancel,
|
IDCANCEL => DialogResult::Cancel,
|
||||||
winsafe::co::DLGID::ABORT => DialogResult::Abort,
|
IDABORT => DialogResult::Abort,
|
||||||
winsafe::co::DLGID::RETRY => DialogResult::Retry,
|
IDRETRY => DialogResult::Retry,
|
||||||
winsafe::co::DLGID::IGNORE => DialogResult::Ignore,
|
IDIGNORE => DialogResult::Ignore,
|
||||||
winsafe::co::DLGID::YES => DialogResult::Yes,
|
IDYES => DialogResult::Yes,
|
||||||
winsafe::co::DLGID::NO => DialogResult::No,
|
IDNO => DialogResult::No,
|
||||||
winsafe::co::DLGID::TRYAGAIN => DialogResult::Tryagain,
|
IDTRYAGAIN => DialogResult::Tryagain,
|
||||||
winsafe::co::DLGID::CONTINUE => DialogResult::Continue,
|
IDCONTINUE => DialogResult::Continue,
|
||||||
_ => DialogResult::Unknown,
|
_ => DialogResult::Unknown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,22 @@
|
|||||||
use super::{dialogs_common::*, dialogs_const::*};
|
use super::{dialogs_common::*, dialogs_const::*};
|
||||||
use velopack::bundle::Manifest;
|
use crate::windows::strings::string_to_wide;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use winsafe::{self as w, co, prelude::*, WString};
|
use velopack::{
|
||||||
use velopack::locator::{auto_locate_app_manifest, LocationContext};
|
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) {
|
pub fn show_restart_required(app: &Manifest) {
|
||||||
show_warn(
|
show_warn(
|
||||||
@@ -34,7 +47,7 @@ pub fn show_update_missing_dependencies_dialog(
|
|||||||
"{} {to} has missing dependencies which need to be installed: {}, would you like to continue?",
|
"{} {to} has missing dependencies which need to be installed: {}, would you like to continue?",
|
||||||
app.title, depedency_string
|
app.title, depedency_string
|
||||||
)
|
)
|
||||||
.as_str(),
|
.as_str(),
|
||||||
Some("Install & Update"),
|
Some("Install & Update"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -58,32 +71,33 @@ pub fn show_uninstall_complete_with_errors_dialog(app_title: &str, log_path: Opt
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut setup_name = WString::from_str(format!("{} Uninstall", app_title));
|
let setup_name = string_to_wide(format!("{} Uninstall", app_title));
|
||||||
let mut instruction = WString::from_str(format!("{} uninstall has completed with errors.", app_title));
|
let instruction = string_to_wide(format!("{} uninstall has completed with errors.", app_title));
|
||||||
let mut content = WString::from_str(
|
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.",
|
"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();
|
let mut config = TASKDIALOGCONFIG::default();
|
||||||
config.dwFlags = co::TDF::ENABLE_HYPERLINKS | co::TDF::SIZE_TO_CONTENT;
|
config.cbSize = std::mem::size_of::<TASKDIALOGCONFIG>() as u32;
|
||||||
config.dwCommonButtons = co::TDCBF::OK;
|
config.dwFlags = TDF_ENABLE_HYPERLINKS | TDF_SIZE_TO_CONTENT;
|
||||||
config.set_pszMainIcon(w::IconIdTdicon::Tdicon(co::TD_ICON::WARNING));
|
config.dwCommonButtons = TDCBF_OK_BUTTON;
|
||||||
config.set_pszWindowTitle(Some(&mut setup_name));
|
config.pszWindowTitle = setup_name.as_pcwstr();
|
||||||
config.set_pszMainInstruction(Some(&mut instruction));
|
config.pszMainInstruction = instruction.as_pcwstr();
|
||||||
config.set_pszContent(Some(&mut content));
|
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 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 let Some(log_path) = log_path {
|
||||||
if log_path.exists() {
|
if log_path.exists() {
|
||||||
config.set_pszFooterIcon(w::IconId::Id(co::TD_ICON::INFORMATION.into()));
|
config.Anonymous2.pszFooterIcon = TD_INFORMATION_ICON;
|
||||||
config.set_pszFooter(Some(&mut footer));
|
config.pszFooter = footer.as_pcwstr();
|
||||||
config.lpCallbackData = log_path as *const PathBuf as usize;
|
config.lpCallbackData = log_path as *const PathBuf as isize;
|
||||||
config.pfCallback = Some(task_dialog_callback);
|
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 {
|
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;
|
return DialogResult::Cancel;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut config: w::TASKDIALOGCONFIG = Default::default();
|
let mut config = TASKDIALOGCONFIG::default();
|
||||||
config.set_pszMainIcon(w::IconIdTdicon::Tdicon(co::TD_ICON::INFORMATION));
|
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 update_name = string_to_wide(format!("{} Update {}", app_title, app_version));
|
||||||
let mut instruction = WString::from_str(format!("{} Update", app_title));
|
let instruction = string_to_wide(format!("{} Update", app_title));
|
||||||
|
let content = string_to_wide(format!(
|
||||||
let mut content = WString::from_str(format!(
|
|
||||||
"There are programs ({}) preventing the {} update from proceeding. \n\n\
|
"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.",
|
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));
|
process_names, app_title));
|
||||||
|
|
||||||
let mut btn_retry_txt = WString::from_str("Retry\nTry again if you've closed the program(s)");
|
let btn_retry_txt = string_to_wide("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 btn_continue_txt = string_to_wide("Continue\nAttempt to close the program(s) automatically");
|
||||||
let mut btn_cancel_txt = WString::from_str("Cancel\nThe update will not continue");
|
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();
|
config.dwFlags = TDF_USE_COMMAND_LINKS;
|
||||||
btn_retry.set_nButtonID(co::DLGID::RETRY.into());
|
config.cButtons = custom_btns.len() as u32;
|
||||||
btn_retry.set_pszButtonText(Some(&mut btn_retry_txt));
|
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();
|
let mut pnbutton = 0;
|
||||||
btn_continue.set_nButtonID(co::DLGID::CONTINUE.into());
|
let mut pnradiobutton = 0;
|
||||||
btn_continue.set_pszButtonText(Some(&mut btn_continue_txt));
|
let mut pfverificationflagchecked = FALSE;
|
||||||
|
|
||||||
let mut btn_cancel = w::TASKDIALOG_BUTTON::default();
|
unsafe { TaskDialogIndirect(&config, Some(&mut pnbutton), Some(&mut pnradiobutton), Some(&mut pfverificationflagchecked)).ok() };
|
||||||
btn_cancel.set_nButtonID(co::DLGID::CANCEL.into());
|
DialogResult::from_win(pnbutton)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_overwrite_repair_dialog(app: &Manifest, root_path: &PathBuf, root_is_default: bool) -> bool {
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// these are the defaults, if we can't detect the current app version - we call it "Repair"
|
let mut config = TASKDIALOGCONFIG::default();
|
||||||
let mut config: w::TASKDIALOGCONFIG = Default::default();
|
config.cbSize = std::mem::size_of::<TASKDIALOGCONFIG>() as u32;
|
||||||
config.set_pszMainIcon(w::IconIdTdicon::Tdicon(co::TD_ICON::WARNING));
|
config.Anonymous1.pszMainIcon = TD_WARNING_ICON;
|
||||||
|
|
||||||
let mut setup_name = WString::from_str(format!("{} Setup {}", app.title, app.version));
|
let setup_name = string_to_wide(format!("{} Setup {}", app.title, app.version));
|
||||||
let mut instruction = WString::from_str(format!("{} is already installed.", app.title));
|
let mut instruction = string_to_wide(format!("{} is already installed.", app.title));
|
||||||
let mut content = WString::from_str(
|
let mut content =
|
||||||
"This application is installed on your computer. If it is not functioning correctly, you can attempt to repair it.",
|
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 mut btn_yes_txt = WString::from_str(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");
|
||||||
let mut btn_cancel_txt = WString::from_str("Cancel\nBackup or save your work first");
|
|
||||||
|
|
||||||
// if we can detect the current app version, we call it "Update" or "Downgrade"
|
// 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()));
|
let old_app = auto_locate_app_manifest(LocationContext::FromSpecifiedRootDir(root_path.to_owned()));
|
||||||
if let Ok(old) = old_app {
|
if let Ok(old) = old_app {
|
||||||
let old_version = old.get_manifest_version();
|
let old_version = old.get_manifest_version();
|
||||||
if old_version < app.version {
|
if old_version < app.version {
|
||||||
instruction = WString::from_str(format!("An older version of {} is installed.", app.title));
|
instruction = string_to_wide(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));
|
content = string_to_wide(format!("Would you like to update from {} to {}?", old_version, app.version));
|
||||||
btn_yes_txt = WString::from_str(format!("Update\nTo version {}", app.version));
|
btn_yes_txt = string_to_wide(format!("Update\nTo version {}", app.version));
|
||||||
config.set_pszMainIcon(w::IconIdTdicon::Tdicon(co::TD_ICON::INFORMATION));
|
config.Anonymous1.pszMainIcon = TD_INFORMATION_ICON;
|
||||||
} else if old_version > app.version {
|
} else if old_version > app.version {
|
||||||
instruction = WString::from_str(format!("A newer version of {} is installed.", app.title));
|
instruction = string_to_wide(format!("A newer version of {} is installed.", app.title));
|
||||||
content = WString::from_str(format!(
|
content = string_to_wide(format!(
|
||||||
"You already have {} installed. Would you like to downgrade this application to an older version?",
|
"You already have {} installed. Would you like to downgrade this application to an older version?",
|
||||||
old_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 {
|
let footer = if root_is_default {
|
||||||
WString::from_str(format!("The install directory is '<A HREF=\"na\">%LocalAppData%\\{}</A>'", app.id))
|
string_to_wide(format!("The install directory is '<A HREF=\"na\">%LocalAppData%\\{}</A>'", app.id))
|
||||||
} else {
|
} 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();
|
let btn_yes = TASKDIALOG_BUTTON { nButtonID: IDYES.0, pszButtonText: btn_yes_txt.as_pcwstr() };
|
||||||
btn_yes.set_nButtonID(co::DLGID::YES.into());
|
let btn_cancel = TASKDIALOG_BUTTON { nButtonID: IDCANCEL.0, pszButtonText: btn_cancel_txt.as_pcwstr() };
|
||||||
btn_yes.set_pszButtonText(Some(&mut btn_yes_txt));
|
let custom_btns = vec![btn_yes, btn_cancel];
|
||||||
|
|
||||||
let mut btn_cancel = w::TASKDIALOG_BUTTON::default();
|
config.dwFlags = TDF_ENABLE_HYPERLINKS | TDF_USE_COMMAND_LINKS;
|
||||||
btn_cancel.set_nButtonID(co::DLGID::CANCEL.into());
|
config.cButtons = custom_btns.len() as u32;
|
||||||
btn_cancel.set_pszButtonText(Some(&mut btn_cancel_txt));
|
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);
|
config.lpCallbackData = root_path as *const PathBuf as isize;
|
||||||
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.pfCallback = Some(task_dialog_callback);
|
config.pfCallback = Some(task_dialog_callback);
|
||||||
|
|
||||||
let (btn, _) = w::TaskDialogIndirect(&config, None).ok().unwrap_or_else(|| (co::DLGID::YES, 0));
|
let mut pnbutton = 0;
|
||||||
return btn == co::DLGID::YES;
|
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 {
|
extern "system" fn task_dialog_callback(_hwnd: HWND, msg: TASKDIALOG_NOTIFICATIONS, _: WPARAM, _: LPARAM, lp_ref_data: isize) -> HRESULT {
|
||||||
if msg == co::TDN::HYPERLINK_CLICKED {
|
if msg == TDN_HYPERLINK_CLICKED {
|
||||||
let raw = lp_ref_data as *const PathBuf;
|
let raw = lp_ref_data as *const PathBuf;
|
||||||
let path: &PathBuf = unsafe { &*raw };
|
let path: &PathBuf = unsafe { &*raw };
|
||||||
let dir = path.to_str().unwrap();
|
let dir = path.to_string_lossy().to_string();
|
||||||
w::HWND::GetDesktopWindow().ShellExecute("open", &dir, None, None, co::SW::SHOWDEFAULT).ok();
|
let dir = string_to_wide(dir);
|
||||||
return co::HRESULT::S_FALSE; // do not close dialog
|
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(
|
pub fn generate_confirm(
|
||||||
@@ -217,42 +223,40 @@ pub fn generate_confirm(
|
|||||||
btns: DialogButton,
|
btns: DialogButton,
|
||||||
ico: DialogIcon,
|
ico: DialogIcon,
|
||||||
) -> Result<DialogResult> {
|
) -> Result<DialogResult> {
|
||||||
let hparent = w::HWND::GetDesktopWindow();
|
let hparent = unsafe { GetDesktopWindow() };
|
||||||
let mut ok_text_buf = WString::from_opt_str(ok_text);
|
let mut ok_text_buf = ok_text.map(string_to_wide);
|
||||||
let mut custom_btns = if ok_text.is_some() {
|
let mut custom_btns = if let Some(ok_text_buf) = ok_text_buf.as_mut() {
|
||||||
let mut td_btn = w::TASKDIALOG_BUTTON::default();
|
let td_btn = TASKDIALOG_BUTTON { nButtonID: IDOK.0, pszButtonText: ok_text_buf.as_pcwstr() };
|
||||||
td_btn.set_nButtonID(co::DLGID::OK.into());
|
vec![td_btn]
|
||||||
td_btn.set_pszButtonText(Some(&mut ok_text_buf));
|
|
||||||
let mut custom_btns = Vec::with_capacity(1);
|
|
||||||
custom_btns.push(td_btn);
|
|
||||||
custom_btns
|
|
||||||
} else {
|
} else {
|
||||||
Vec::<w::TASKDIALOG_BUTTON>::default()
|
Vec::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut tdc = w::TASKDIALOGCONFIG::default();
|
let mut tdc = TASKDIALOGCONFIG { hwndParent: hparent, ..Default::default() };
|
||||||
tdc.hwndParent = unsafe { hparent.raw_copy() };
|
tdc.cbSize = std::mem::size_of::<TASKDIALOGCONFIG>() as u32;
|
||||||
tdc.dwFlags = co::TDF::ALLOW_DIALOG_CANCELLATION | co::TDF::POSITION_RELATIVE_TO_WINDOW;
|
tdc.dwFlags = TDF_ALLOW_DIALOG_CANCELLATION | TDF_POSITION_RELATIVE_TO_WINDOW;
|
||||||
tdc.dwCommonButtons = btns.to_win();
|
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() {
|
if !custom_btns.is_empty() {
|
||||||
tdc.set_pButtons(Some(&mut custom_btns));
|
tdc.cButtons = custom_btns.len() as u32;
|
||||||
|
tdc.pButtons = custom_btns.as_mut_ptr();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut title_buf = WString::from_str(title);
|
let title_buf = string_to_wide(title);
|
||||||
tdc.set_pszWindowTitle(Some(&mut title_buf));
|
tdc.pszWindowTitle = title_buf.as_pcwstr();
|
||||||
|
|
||||||
let mut header_buf = WString::from_opt_str(header);
|
let mut header_buf = header.map(string_to_wide);
|
||||||
if header.is_some() {
|
if let Some(header_buf) = header_buf.as_mut() {
|
||||||
tdc.set_pszMainInstruction(Some(&mut header_buf));
|
tdc.pszMainInstruction = header_buf.as_pcwstr();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut body_buf = WString::from_str(body);
|
let body_buf = string_to_wide(body);
|
||||||
tdc.set_pszContent(Some(&mut body_buf));
|
tdc.pszContent = body_buf.as_pcwstr();
|
||||||
|
|
||||||
let result = w::TaskDialogIndirect(&tdc, None).map(|(dlg_id, _)| dlg_id)?;
|
let mut pnbutton = 0;
|
||||||
Ok(DialogResult::from_win(result))
|
unsafe { TaskDialogIndirect(&tdc, Some(&mut pnbutton), None, None).expect("didnt work") };
|
||||||
|
Ok(DialogResult::from_win(pnbutton))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_alert(
|
pub fn generate_alert(
|
||||||
@@ -263,6 +267,28 @@ pub fn generate_alert(
|
|||||||
btns: DialogButton,
|
btns: DialogButton,
|
||||||
ico: DialogIcon,
|
ico: DialogIcon,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let _ = generate_confirm(title, header, body, ok_text, btns, ico).map(|_| ())?;
|
let _ = generate_confirm(title, header, body, ok_text, btns, ico)?;
|
||||||
Ok(())
|
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 runtimes;
|
||||||
pub mod splash;
|
pub mod splash;
|
||||||
pub mod known_path;
|
pub mod known_path;
|
||||||
|
|
||||||
pub mod strings;
|
pub mod strings;
|
||||||
pub mod registry;
|
pub mod registry;
|
||||||
pub mod webview2;
|
pub mod webview2;
|
||||||
@@ -14,4 +15,4 @@ mod util;
|
|||||||
|
|
||||||
pub use self_delete::*;
|
pub use self_delete::*;
|
||||||
pub use shortcuts::*;
|
pub use shortcuts::*;
|
||||||
pub use util::*;
|
pub use util::*;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use chrono::{Datelike, Local as DateTime};
|
use chrono::{Datelike, Local as DateTime};
|
||||||
use velopack::locator::VelopackLocator;
|
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";
|
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 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 = 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 short_version = locator.get_manifest_version_short_string();
|
||||||
|
|
||||||
let now = DateTime::now();
|
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_cmd = format!("\"{}\" --uninstall", updater_path);
|
||||||
let uninstall_quiet: String = format!("\"{}\" --uninstall --silent", updater_path);
|
let uninstall_quiet: String = format!("\"{}\" --uninstall --silent", updater_path);
|
||||||
|
|
||||||
let reg_uninstall =
|
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
||||||
w::HKEY::CURRENT_USER.RegCreateKeyEx(UNINSTALL_REGISTRY_KEY, None, co::REG_OPTION::NoValue, co::KEY::CREATE_SUB_KEY, None)?.0;
|
let (reg_uninstall, _reg_uninstall_disp) = hkcu.create_subkey(UNINSTALL_REGISTRY_KEY)?;
|
||||||
let reg_app = reg_uninstall.RegCreateKeyEx(&app_id, None, co::REG_OPTION::NoValue, co::KEY::ALL_ACCESS, None)?.0;
|
let (reg_app, _reg_app_disp) = reg_uninstall.create_subkey(&app_id)?;
|
||||||
reg_app.RegSetKeyValue(None, Some("DisplayIcon"), w::RegistryValue::Sz(main_exe_path))?;
|
|
||||||
reg_app.RegSetKeyValue(None, Some("DisplayName"), w::RegistryValue::Sz(app_title))?;
|
let u32true = 1u32;
|
||||||
reg_app.RegSetKeyValue(None, Some("DisplayVersion"), w::RegistryValue::Sz(short_version))?;
|
let language = 0x0409u32;
|
||||||
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.set_value("DisplayIcon", &main_exe_path)?;
|
||||||
reg_app.RegSetKeyValue(None, Some("Publisher"), w::RegistryValue::Sz(app_authors))?;
|
reg_app.set_value("DisplayName", &app_title)?;
|
||||||
reg_app.RegSetKeyValue(None, Some("QuietUninstallString"), w::RegistryValue::Sz(uninstall_quiet))?;
|
reg_app.set_value("DisplayVersion", &short_version)?;
|
||||||
reg_app.RegSetKeyValue(None, Some("UninstallString"), w::RegistryValue::Sz(uninstall_cmd))?;
|
reg_app.set_value("InstallDate", &formatted_date)?;
|
||||||
reg_app.RegSetKeyValue(None, Some("EstimatedSize"), w::RegistryValue::Dword((folder_size / 1024).try_into()?))?;
|
reg_app.set_value("InstallLocation", &root_path_str)?;
|
||||||
reg_app.RegSetKeyValue(None, Some("NoModify"), w::RegistryValue::Dword(1))?;
|
reg_app.set_value("Publisher", &app_authors)?;
|
||||||
reg_app.RegSetKeyValue(None, Some("NoRepair"), w::RegistryValue::Dword(1))?;
|
reg_app.set_value("QuietUninstallString", &uninstall_quiet)?;
|
||||||
reg_app.RegSetKeyValue(None, Some("Language"), w::RegistryValue::Dword(0x0409))?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_uninstall_entry(locator: &VelopackLocator) -> Result<()> {
|
pub fn remove_uninstall_entry(locator: &VelopackLocator) -> Result<()> {
|
||||||
info!("Removing uninstall registry keys...");
|
info!("Removing uninstall registry keys...");
|
||||||
let app_id = locator.get_manifest_id();
|
let app_id = locator.get_manifest_id();
|
||||||
let reg_uninstall =
|
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
||||||
w::HKEY::CURRENT_USER.RegCreateKeyEx(UNINSTALL_REGISTRY_KEY, None, co::REG_OPTION::NoValue, co::KEY::CREATE_SUB_KEY, None)?.0;
|
let (reg_uninstall, _reg_uninstall_disp) = hkcu.create_subkey(UNINSTALL_REGISTRY_KEY)?;
|
||||||
reg_uninstall.RegDeleteKey(&app_id)?;
|
reg_uninstall.delete_subkey_all(&app_id)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use regex::Regex;
|
|||||||
use std::process::Command as Process;
|
use std::process::Command as Process;
|
||||||
use std::{collections::HashMap, fs, path::Path};
|
use std::{collections::HashMap, fs, path::Path};
|
||||||
use velopack::download;
|
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_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";
|
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 {
|
fn is_installed(&self) -> bool {
|
||||||
let lm = w::HKEY::LOCAL_MACHINE;
|
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
|
||||||
let key = lm.RegOpenKeyEx(Some(NDP_REG_KEY), co::REG_OPTION::NoValue, co::KEY::READ);
|
match hklm.open_subkey(NDP_REG_KEY) {
|
||||||
if key.is_err() {
|
Ok(ndp) => {
|
||||||
// key doesn't exist, so .net framework not installed
|
let dword_val: std::io::Result<u32> = ndp.get_value("Release");
|
||||||
return false;
|
if let Ok(rel) = dword_val {
|
||||||
}
|
rel >= self.release_version
|
||||||
let release = key.unwrap().RegGetValue(None, Some("Release"));
|
} else {
|
||||||
if release.is_err() {
|
false // key doesn't exist, so .net framework not installed
|
||||||
// key doesn't exist, so .net framework not installed
|
}
|
||||||
return false;
|
}
|
||||||
}
|
Err(_) => false, // key doesn't exist, so .net framework not installed
|
||||||
match release.unwrap() {
|
|
||||||
w::RegistryValue::Dword(v) => return v >= self.release_version,
|
|
||||||
_ => return false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,8 +183,8 @@ impl RuntimeInfo for VCRedistInfo {
|
|||||||
|
|
||||||
fn is_installed(&self) -> bool {
|
fn is_installed(&self) -> bool {
|
||||||
let mut installed_programs = HashMap::new();
|
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, KEY_READ | 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_64KEY);
|
||||||
let (my_major, my_minor, my_build, _) = util::parse_version(&self.min_version).unwrap();
|
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();
|
let reg = Regex::new(r"(?i)Microsoft Visual C\+\+(.*)Redistributable").unwrap();
|
||||||
for (k, v) in installed_programs {
|
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) {
|
fn get_installed_programs(map: &mut HashMap<String, String>, access_rights: u32) {
|
||||||
let key = w::HKEY::LOCAL_MACHINE.RegOpenKeyEx(Some(UNINSTALL_REG_KEY), co::REG_OPTION::NoValue, access_rights);
|
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(view) = key {
|
||||||
if let Ok(iter) = view.RegEnumKeyEx() {
|
for key_result in view.enum_keys() {
|
||||||
for key_result in iter {
|
if let Ok(key_name) = key_result {
|
||||||
if let Ok(key_name) = key_result {
|
let subkey = view.open_subkey_with_flags(key_name, access_rights);
|
||||||
let subkey = view.RegOpenKeyEx(Some(&key_name), co::REG_OPTION::NoValue, access_rights);
|
if subkey.is_err() {
|
||||||
if subkey.is_err() {
|
continue;
|
||||||
continue;
|
}
|
||||||
}
|
let subkey = subkey.unwrap();
|
||||||
let subkey = subkey.unwrap();
|
let name: std::io::Result<String> = subkey.get_value("DisplayName");
|
||||||
let name = subkey.RegQueryValueEx(Some("DisplayName"));
|
let version: std::io::Result<String> = subkey.get_value("DisplayVersion");
|
||||||
let version = subkey.RegQueryValueEx(Some("DisplayVersion"));
|
if name.is_ok() && version.is_ok() {
|
||||||
if name.is_ok() && version.is_ok() {
|
map.insert(name.unwrap(), version.unwrap());
|
||||||
if let w::RegistryValue::Sz(display_name) = name.unwrap() {
|
|
||||||
if let w::RegistryValue::Sz(display_version) = version.unwrap() {
|
|
||||||
map.insert(display_name, display_version);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,7 +234,7 @@ fn get_installed_programs(map: &mut HashMap<String, String>, access_rights: co::
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_get_installed_programs_returns_visual_studio() {
|
fn test_get_installed_programs_returns_visual_studio() {
|
||||||
let mut map = HashMap::new();
|
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"));
|
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> {
|
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);
|
info!("Running installer: '{}', args={:?}", installer_path, args);
|
||||||
let mut cmd = Process::new(installer_path).args(&args).spawn()?;
|
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 anyhow::{bail, Result};
|
||||||
use image::{codecs::gif::GifDecoder, AnimationDecoder, DynamicImage, ImageFormat, ImageReader};
|
use image::{codecs::gif::GifDecoder, AnimationDecoder, DynamicImage, ImageFormat, ImageReader};
|
||||||
use std::sync::atomic::{AtomicI16, Ordering};
|
use std::sync::mpsc::{self, Receiver, Sender};
|
||||||
use std::{
|
use std::{io::Cursor, thread};
|
||||||
cell::RefCell,
|
use windows::{
|
||||||
io::Cursor,
|
core::HRESULT,
|
||||||
ops::Deref,
|
Win32::{
|
||||||
rc::Rc,
|
Foundation::{COLORREF, HINSTANCE, HWND, LPARAM, LRESULT, POINT, RECT, S_OK, WPARAM},
|
||||||
sync::mpsc::{self, Receiver, Sender},
|
Graphics::Gdi::*,
|
||||||
thread,
|
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 TMR_GIF: usize = 1;
|
||||||
const MSG_NOMESSAGE: i16 = -99;
|
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 || {
|
thread::spawn(move || {
|
||||||
info!("Showing splash screen immediately...");
|
info!("Showing splash screen immediately...");
|
||||||
if imgstream.is_some() {
|
if imgstream.is_some() {
|
||||||
let _ = SplashWindow::new(app_name, imgstream.unwrap(), rx).and_then(|w| {
|
if let Err(e) = unsafe { SplashWindow::run(app_name, imgstream.unwrap(), rx) } {
|
||||||
w.run()?;
|
error!("Failed to show splash screen: {:?}", e);
|
||||||
Ok(())
|
}
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
let setup_name = format!("{} Setup", app_name);
|
let setup_name = format!("{} Setup", app_name);
|
||||||
let content = format!("Installing {}...", 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
|
tx
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
struct SplashWindow {
|
||||||
pub struct SplashWindow {
|
frames: Vec<HBITMAP>,
|
||||||
wnd: gui::WindowMain,
|
rx: Receiver<i16>,
|
||||||
frames: Rc<Vec<DeleteObjectGuard<w::HBITMAP>>>,
|
progress: i16,
|
||||||
rx: Rc<Receiver<i16>>,
|
frame_idx: usize,
|
||||||
delay: u16,
|
w: i32,
|
||||||
progress: Rc<RefCell<i16>>,
|
h: i32,
|
||||||
frame_idx: Rc<RefCell<usize>>,
|
hdc_screen: HDC,
|
||||||
w: u16,
|
|
||||||
h: u16,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn average(numbers: &[u16]) -> u16 {
|
fn average(numbers: &[u32]) -> u32 {
|
||||||
let sum: u16 = numbers.iter().sum();
|
let sum: u32 = numbers.iter().sum();
|
||||||
let count = numbers.len() as u16;
|
let count = numbers.len() as u32;
|
||||||
sum / count
|
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 {
|
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 delays = Vec::new();
|
||||||
let mut frames = Vec::new();
|
let mut frames = Vec::new();
|
||||||
|
|
||||||
let fmt_cursor = Cursor::new(&img_stream);
|
let fmt_cursor = Cursor::new(&img_stream);
|
||||||
let fmt_reader = ImageReader::new(fmt_cursor).with_guessed_format()?;
|
let fmt_reader = ImageReader::new(fmt_cursor).with_guessed_format()?;
|
||||||
let fmt = fmt_reader.format();
|
let fmt = fmt_reader.format();
|
||||||
|
|
||||||
let dims = &fmt_reader.into_dimensions()?;
|
let dims = &fmt_reader.into_dimensions()?;
|
||||||
let w: u16 = u16::try_from(dims.0)?;
|
let w: i32 = i32::try_from(dims.0)?;
|
||||||
let h: u16 = u16::try_from(dims.1)?;
|
let h: i32 = i32::try_from(dims.1)?;
|
||||||
|
|
||||||
if Some(ImageFormat::Gif) == fmt {
|
if Some(ImageFormat::Gif) == fmt {
|
||||||
info!("Image is animated GIF ({}x{}), loading frames...", w, h);
|
info!("Image is animated GIF ({}x{}), loading frames...", w, h);
|
||||||
@@ -93,22 +104,22 @@ impl SplashWindow {
|
|||||||
for frame in dec_frames.into_iter() {
|
for frame in dec_frames.into_iter() {
|
||||||
let frame = frame?;
|
let frame = frame?;
|
||||||
let (num, dem) = frame.delay().numer_denom_ms();
|
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 dynamic = DynamicImage::from(frame.buffer().to_owned());
|
||||||
let mut vec = dynamic.to_rgba8().to_vec();
|
let mut vec = dynamic.to_rgba8().to_vec();
|
||||||
convert_rgba_to_bgra(&mut 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);
|
frames.push(bitmap);
|
||||||
}
|
}
|
||||||
info!("Successfully loaded {} frames.", frames.len());
|
info!("Successfully loaded {} frames.", frames.len());
|
||||||
} else {
|
} else {
|
||||||
info!("Loading static image (detected {:?})...", fmt);
|
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_cursor = Cursor::new(&img_stream);
|
||||||
let img_decoder = ImageReader::new(img_cursor).with_guessed_format()?.decode()?;
|
let img_decoder = ImageReader::new(img_cursor).with_guessed_format()?.decode()?;
|
||||||
let mut vec = img_decoder.to_rgba8().to_vec();
|
let mut vec = img_decoder.to_rgba8().to_vec();
|
||||||
convert_rgba_to_bgra(&mut 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);
|
frames.push(bitmap);
|
||||||
info!("Successfully loaded.");
|
info!("Successfully loaded.");
|
||||||
}
|
}
|
||||||
@@ -117,256 +128,268 @@ impl SplashWindow {
|
|||||||
// support a variable frame delay in the future.
|
// support a variable frame delay in the future.
|
||||||
let delay = average(&delays);
|
let delay = average(&delays);
|
||||||
|
|
||||||
let wnd = gui::WindowMain::new(gui::WindowMainOpts {
|
let class_name = string_to_wide("VelopackSetupSplashWindow");
|
||||||
class_icon: gui::Icon::Idi(co::IDI::APPLICATION),
|
let app_name = string_to_wide(&app_name);
|
||||||
class_cursor: gui::Cursor::Idc(co::IDC::APPSTARTING),
|
|
||||||
class_style: co::CS::HREDRAW | co::CS::VREDRAW,
|
let h_instance: HINSTANCE = GetModuleHandleW(None)?.into();
|
||||||
class_name: "VelopackSetupSplashWindow".to_owned(),
|
|
||||||
title: app_name,
|
let wnd_class = WNDCLASSEXW {
|
||||||
size: (w.into(), h.into()),
|
cbSize: std::mem::size_of::<WNDCLASSEXW>() as u32,
|
||||||
ex_style: co::WS_EX::NoValue,
|
style: CS_HREDRAW | CS_VREDRAW,
|
||||||
style: co::WS::POPUP,
|
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()
|
..Default::default()
|
||||||
});
|
};
|
||||||
|
|
||||||
let frames = Rc::new(frames);
|
let class_id = unsafe { RegisterClassExW(&wnd_class) };
|
||||||
let rx = Rc::new(rx);
|
if class_id == 0 {
|
||||||
let progress = Rc::new(RefCell::new(0));
|
// if class already registered we can ignore
|
||||||
let frame_idx = Rc::new(RefCell::new(0));
|
let err = std::io::Error::last_os_error();
|
||||||
let mut new_self = Self { wnd, frames, delay, frame_idx, w, h, rx, progress };
|
if err.raw_os_error() != Some(1410) {
|
||||||
new_self.events();
|
bail!("Failed to register window class: {:?}", err);
|
||||||
Ok(new_self)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&self) -> Result<i32> {
|
// center the window on the screen containing the cursor
|
||||||
let res = self.wnd.run_main(Some(co::SW::SHOWNOACTIVATE));
|
let mut lppoint = POINT::default();
|
||||||
if res.is_err() {
|
GetCursorPos(&mut lppoint)?;
|
||||||
error!("Error Showing Splash Window: {:?}", res);
|
|
||||||
bail!("Error Showing Splash Window: {:?}", res);
|
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 {
|
} else {
|
||||||
info!("Splash Window Closed");
|
// fallback to work area if monitor info is not available
|
||||||
}
|
let mut rc_work_area: RECT = Default::default();
|
||||||
Ok(res.unwrap())
|
SystemParametersInfoW(
|
||||||
}
|
SPI_GETWORKAREA,
|
||||||
|
0,
|
||||||
fn events(&mut self) {
|
Some(&mut rc_work_area as *mut RECT as *mut _),
|
||||||
let self2 = self.clone();
|
SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS(0),
|
||||||
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)
|
|
||||||
});
|
|
||||||
|
|
||||||
self.wnd.on().wm_nc_hit_test(|_m| {
|
|
||||||
Ok(co::HT::CAPTION) // make the window draggable
|
|
||||||
});
|
|
||||||
|
|
||||||
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 {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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())?;
|
|
||||||
|
|
||||||
// load image into hdc_bitmap
|
|
||||||
let hdc_bitmap = hdc_screen.CreateCompatibleDC()?;
|
|
||||||
let _bitmap_old = hdc_bitmap.SelectObject(h_bitmap)?;
|
|
||||||
|
|
||||||
// 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)?;
|
|
||||||
|
|
||||||
// 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,
|
|
||||||
)?;
|
)?;
|
||||||
|
lppoint.x = (rc_work_area.left + rc_work_area.right - w) / 2;
|
||||||
|
lppoint.y = (rc_work_area.top + rc_work_area.bottom - h) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
// draw progress bar to hdc_mem
|
let hwnd = CreateWindowExW(
|
||||||
let progress = self2.progress.borrow();
|
WS_EX_TOOLWINDOW | WS_EX_TOPMOST | WS_EX_NOACTIVATE,
|
||||||
let progress_brush = w::HBRUSH::CreateSolidBrush(w::COLORREF::new(0, 255, 0))?;
|
class_name.as_pcwstr(),
|
||||||
let progress_width = (rect.right as f32 * (*progress as f32 / 100.0)) as i32;
|
app_name.as_pcwstr(),
|
||||||
let progress_rect = w::RECT { left: 0, bottom: rect.bottom, right: progress_width, top: rect.bottom - 10 };
|
WS_CLIPCHILDREN | WS_POPUP,
|
||||||
hdc_mem.FillRect(progress_rect, &progress_brush)?;
|
lppoint.x,
|
||||||
|
lppoint.y,
|
||||||
|
w,
|
||||||
|
h,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Some(h_instance),
|
||||||
|
None,
|
||||||
|
)?;
|
||||||
|
|
||||||
// finally, copy hdc_mem to hdc
|
let desktop = unsafe { GetDesktopWindow() };
|
||||||
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 hdc_screen = unsafe { GetDC(Some(desktop)) };
|
||||||
|
|
||||||
Ok(())
|
let data_ptr = Box::into_raw(Box::new(Self { frames, rx, frame_idx: 0, w, h, progress: 0, hdc_screen }));
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const TDM_SET_PROGRESS_BAR_MARQUEE: co::WM = unsafe { co::WM::from_raw(1131) };
|
SetWindowLongPtrW(hwnd, GWL_USERDATA, data_ptr as isize);
|
||||||
pub const TDM_SET_MARQUEE_PROGRESS_BAR: co::WM = unsafe { co::WM::from_raw(1127) };
|
let _ = ShowWindow(hwnd, SW_SHOWNOACTIVATE);
|
||||||
pub const TDM_SET_PROGRESS_BAR_POS: co::WM = unsafe { co::WM::from_raw(1130) };
|
SetTimer(Some(hwnd), TMR_GIF, delay, None);
|
||||||
|
|
||||||
struct MsgSetProgressMarqueeOnOff {
|
let mut msg = MSG::default();
|
||||||
is_marquee_on: bool,
|
let _ = PeekMessageW(&mut msg, Some(hwnd), 0, 0, PEEK_MESSAGE_REMOVE_TYPE(0)); // invoke creating message queue
|
||||||
}
|
|
||||||
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 {
|
while GetMessageW(&mut msg, None, 0, 0).as_bool() {
|
||||||
is_marquee_on: bool,
|
if msg.message == WM_QUIT {
|
||||||
}
|
|
||||||
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>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ComCtlProgressWindow {
|
|
||||||
pub fn set_progress(&self, value: i16) {
|
|
||||||
self.last_progress.store(value, Ordering::SeqCst);
|
|
||||||
}
|
|
||||||
pub fn get_progress(&self) -> i16 {
|
|
||||||
self.last_progress.load(Ordering::SeqCst)
|
|
||||||
}
|
|
||||||
pub fn get_next_message(&self) -> i16 {
|
|
||||||
let mut progress: i16 = MSG_NOMESSAGE;
|
|
||||||
loop {
|
|
||||||
let msg = self.rx.try_recv().unwrap_or(MSG_NOMESSAGE);
|
|
||||||
if msg == MSG_NOMESSAGE {
|
|
||||||
break;
|
break;
|
||||||
} else {
|
}
|
||||||
progress = 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
|
||||||
|
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 = 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 = CreateCompatibleDC(Some(self.hdc_screen));
|
||||||
|
let bitmap_old = SelectObject(hdc_bitmap, h_bitmap.into());
|
||||||
|
|
||||||
|
// draw background to hdc_mem
|
||||||
|
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
|
||||||
|
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 = 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
|
||||||
|
let _ = BitBlt(hdc, 0, 0, self.w, self.h, Some(hdc_mem), 0, 0, SRCCOPY);
|
||||||
|
|
||||||
|
// 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
progress
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct ComCtlProgressWindow {
|
||||||
|
rx: Receiver<i16>,
|
||||||
|
last_progress: i16,
|
||||||
|
}
|
||||||
|
|
||||||
fn show_com_ctl_progress_dialog(rx: Receiver<i16>, window_title: &str, content: &str) {
|
fn show_com_ctl_progress_dialog(rx: Receiver<i16>, window_title: &str, content: &str) {
|
||||||
let mut window_title = WString::from_str(window_title);
|
let window_title = string_to_wide(window_title);
|
||||||
let mut content = WString::from_str(content);
|
let content = string_to_wide(content);
|
||||||
|
let ok_text = string_to_wide("Hide");
|
||||||
|
|
||||||
let mut ok_text_buf = WString::from_str("Hide");
|
let td_btn = TASKDIALOG_BUTTON {
|
||||||
let mut td_btn = w::TASKDIALOG_BUTTON::default();
|
nButtonID: 1, // OK button id
|
||||||
td_btn.set_nButtonID(co::DLGID::OK.into());
|
pszButtonText: ok_text.as_pcwstr(),
|
||||||
td_btn.set_pszButtonText(Some(&mut ok_text_buf));
|
};
|
||||||
let mut custom_btns = Vec::with_capacity(1);
|
let custom_btns = vec![td_btn];
|
||||||
custom_btns.push(td_btn);
|
|
||||||
|
|
||||||
let mut config: w::TASKDIALOGCONFIG = Default::default();
|
let mut config: TASKDIALOGCONFIG = Default::default();
|
||||||
config.dwFlags = co::TDF::SIZE_TO_CONTENT | co::TDF::SHOW_PROGRESS_BAR | co::TDF::CALLBACK_TIMER;
|
config.cbSize = std::mem::size_of::<TASKDIALOGCONFIG>() as u32;
|
||||||
config.set_pszMainIcon(w::IconIdTdicon::Tdicon(co::TD_ICON::INFORMATION));
|
config.dwFlags = TDF_SIZE_TO_CONTENT | TDF_SHOW_PROGRESS_BAR | TDF_CALLBACK_TIMER;
|
||||||
config.set_pszWindowTitle(Some(&mut window_title));
|
config.pszWindowTitle = window_title.as_pcwstr();
|
||||||
config.set_pszMainInstruction(Some(&mut content));
|
config.pszMainInstruction = content.as_pcwstr();
|
||||||
config.set_pButtons(Some(&mut custom_btns));
|
config.pButtons = custom_btns.as_ptr();
|
||||||
|
config.cButtons = custom_btns.len() as u32;
|
||||||
|
config.nDefaultButton = 1;
|
||||||
|
config.Anonymous1.pszMainIcon = TD_INFORMATION_ICON;
|
||||||
|
|
||||||
// if (_icon != null) {
|
let me = ComCtlProgressWindow { rx, last_progress: 0 };
|
||||||
// config.dwFlags |= TASKDIALOG_FLAGS.TDF_USE_HICON_MAIN;
|
let data_ptr = Box::into_raw(Box::new(me));
|
||||||
// config.mainIcon = _icon.Handle;
|
config.lpCallbackData = data_ptr as isize;
|
||||||
// }
|
|
||||||
|
|
||||||
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);
|
config.pfCallback = Some(task_dialog_callback);
|
||||||
|
|
||||||
let _ = w::TaskDialogIndirect(&config, None);
|
unsafe {
|
||||||
|
let _ = TaskDialogIndirect(&config, None, None, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = unsafe { Box::from_raw(data_ptr) }; // This will drop the ComCtlProgressWindow instance
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "system" fn task_dialog_callback(hwnd: w::HWND, msg: co::TDN, _: usize, _: isize, lp_ref_data: usize) -> co::HRESULT {
|
fn drain_and_get_next_message(rx: &Receiver<i16>) -> i16 {
|
||||||
let raw = lp_ref_data as *const ComCtlProgressWindow;
|
let mut progress: i16 = MSG_NOMESSAGE;
|
||||||
let me: &ComCtlProgressWindow = unsafe { &*raw };
|
loop {
|
||||||
|
let msg = rx.try_recv().unwrap_or(MSG_NOMESSAGE);
|
||||||
|
if msg == MSG_NOMESSAGE {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
progress = msg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
progress
|
||||||
|
}
|
||||||
|
|
||||||
if msg == co::TDN::TIMER {
|
unsafe extern "system" fn task_dialog_callback(
|
||||||
let next_message = me.get_next_message();
|
hwnd: HWND,
|
||||||
if next_message == MSG_CLOSE {
|
msg: TASKDIALOG_NOTIFICATIONS,
|
||||||
let _ = hwnd.EndDialog(0);
|
_wparam: WPARAM,
|
||||||
return co::HRESULT::S_OK;
|
_lparam: LPARAM,
|
||||||
} else if next_message == MSG_INDEFINITE {
|
lp_ref_data: isize,
|
||||||
hwnd.SendMessage(MsgSetProgressMarqueeOnOff { is_marquee_on: true });
|
) -> HRESULT {
|
||||||
hwnd.SendMessage(MsgSetProgressMarqueeMode { is_marquee_on: true });
|
let raw = lp_ref_data as *mut ComCtlProgressWindow;
|
||||||
me.set_progress(MSG_INDEFINITE);
|
|
||||||
} else if next_message >= 0 {
|
if let Some(me) = raw.as_mut() {
|
||||||
if me.get_progress() < 0 {
|
if msg == TDN_TIMER {
|
||||||
hwnd.SendMessage(MsgSetProgressMarqueeOnOff { is_marquee_on: false });
|
let next_message = drain_and_get_next_message(&me.rx);
|
||||||
hwnd.SendMessage(MsgSetProgressMarqueeMode { is_marquee_on: false });
|
if next_message == MSG_CLOSE {
|
||||||
|
let _ = EndDialog(hwnd, 0);
|
||||||
|
} else if next_message == 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.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]
|
#[test]
|
||||||
@@ -374,8 +397,16 @@ extern "system" fn task_dialog_callback(hwnd: w::HWND, msg: co::TDN, _: usize, _
|
|||||||
fn show_test_gif() {
|
fn show_test_gif() {
|
||||||
let rd = std::fs::read(r"C:\Source\Clowd\artwork\splash.gif").unwrap();
|
let rd = std::fs::read(r"C:\Source\Clowd\artwork\splash.gif").unwrap();
|
||||||
let tx = show_splash_dialog("osu!".to_string(), Some(rd));
|
let tx = show_splash_dialog("osu!".to_string(), Some(rd));
|
||||||
tx.send(80).unwrap();
|
let _ = tx.send(25);
|
||||||
std::thread::sleep(std::time::Duration::from_secs(6));
|
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]
|
#[test]
|
||||||
|
|||||||
@@ -1,34 +1,88 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use windows::core::{PCWSTR, PWSTR};
|
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> {
|
pub fn string_to_u16<P: AsRef<str>>(input: P) -> Vec<u16> {
|
||||||
let input = input.as_ref();
|
let input = input.as_ref();
|
||||||
input.encode_utf16().chain(Some(0)).collect::<Vec<u16>>()
|
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];
|
fn to_wide_slice(&self) -> &[u16];
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WideString for PWSTR {
|
impl WideStringRef for PWSTR {
|
||||||
fn to_wide_slice(&self) -> &[u16] {
|
fn to_wide_slice(&self) -> &[u16] {
|
||||||
unsafe { self.as_wide() }
|
unsafe { self.as_wide() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WideString for PCWSTR {
|
impl WideStringRef for PCWSTR {
|
||||||
fn to_wide_slice(&self) -> &[u16] {
|
fn to_wide_slice(&self) -> &[u16] {
|
||||||
unsafe { self.as_wide() }
|
unsafe { self.as_wide() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WideString for Vec<u16> {
|
impl WideStringRef for Vec<u16> {
|
||||||
fn to_wide_slice(&self) -> &[u16] {
|
fn to_wide_slice(&self) -> &[u16] {
|
||||||
self.as_ref()
|
self.as_ref()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WideString for &Vec<u16> {
|
impl WideStringRef for &Vec<u16> {
|
||||||
fn to_wide_slice(&self) -> &[u16] {
|
fn to_wide_slice(&self) -> &[u16] {
|
||||||
self.as_ref()
|
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] {
|
fn to_wide_slice(&self) -> &[u16] {
|
||||||
self.as_ref()
|
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 slice = input.to_wide_slice();
|
||||||
let null_pos = slice.iter().position(|&x| x == 0).unwrap_or(slice.len());
|
let null_pos = slice.iter().position(|&x| x == 0).unwrap_or(slice.len());
|
||||||
let trimmed_slice = &slice[..null_pos];
|
let trimmed_slice = &slice[..null_pos];
|
||||||
String::from_utf16_lossy(trimmed_slice)
|
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 slice = input.to_wide_slice();
|
||||||
let null_pos = slice.iter().position(|&x| x == 0).unwrap_or(slice.len());
|
let null_pos = slice.iter().position(|&x| x == 0).unwrap_or(slice.len());
|
||||||
let trimmed_slice = &slice[..null_pos];
|
let trimmed_slice = &slice[..null_pos];
|
||||||
Ok(String::from_utf16(trimmed_slice)?)
|
Ok(String::from_utf16(trimmed_slice)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// pub fn pwstr_to_string(input: PWSTR) -> Result<String> {
|
// pub fn pwstr_to_string(input: PWSTR) -> Result<String> {
|
||||||
// unsafe {
|
// unsafe {
|
||||||
// let hstring = input.to_hstring();
|
// let hstring = input.to_hstring();
|
||||||
|
|||||||
@@ -5,16 +5,16 @@ use common::*;
|
|||||||
use std::hint::assert_unchecked;
|
use std::hint::assert_unchecked;
|
||||||
use std::{fs, path::Path, path::PathBuf};
|
use std::{fs, path::Path, path::PathBuf};
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
use velopack_bins::*;
|
|
||||||
|
|
||||||
|
use velopack_bins::*;
|
||||||
|
use velopack_bins::windows::known_path;
|
||||||
use velopack::bundle::load_bundle_from_file;
|
use velopack::bundle::load_bundle_from_file;
|
||||||
use velopack::locator::{auto_locate_app_manifest, LocationContext};
|
use velopack::locator::{auto_locate_app_manifest, LocationContext};
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
use winsafe::{self as w, co};
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
#[test]
|
#[test]
|
||||||
pub fn test_install_apply_uninstall() {
|
pub fn test_install_apply_uninstall() {
|
||||||
|
|
||||||
dialogs::set_silent(true);
|
dialogs::set_silent(true);
|
||||||
|
|
||||||
let fixtures = find_fixtures();
|
let fixtures = find_fixtures();
|
||||||
@@ -22,10 +22,8 @@ pub fn test_install_apply_uninstall() {
|
|||||||
let app_id = "AvaloniaCrossPlat";
|
let app_id = "AvaloniaCrossPlat";
|
||||||
let pkg_name = "AvaloniaCrossPlat-1.0.11-win-full.nupkg";
|
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 = PathBuf::from(known_path::get_start_menu().unwrap());
|
||||||
let start_menu = Path::new(&start_menu).join("Programs");
|
let desktop = PathBuf::from(known_path::get_user_desktop().unwrap());
|
||||||
let desktop = w::SHGetKnownFolderPath(&co::KNOWNFOLDERID::Desktop, co::KF::DONT_UNEXPAND, None).unwrap();
|
|
||||||
let desktop = Path::new(&desktop);
|
|
||||||
|
|
||||||
let lnk_start_1 = start_menu.join(format!("{}.lnk", app_id));
|
let lnk_start_1 = start_menu.join(format!("{}.lnk", app_id));
|
||||||
let lnk_desktop_1 = desktop.join(format!("{}.lnk", app_id));
|
let lnk_desktop_1 = desktop.join(format!("{}.lnk", app_id));
|
||||||
|
|||||||
Reference in New Issue
Block a user