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; | ||||||
|   | |||||||
| @@ -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