From 8ce51cec3baf4ed88d80c59bf3bbe96fd369c7a0 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Wed, 31 Jan 2024 21:02:48 +0200 Subject: [PATCH] feat: retain cli args when relaunching after update, closes #7402 (#7718) * feat: retain cli args when relaunching after update, closes #7402 * 1.61 compatible OsString join * fix msi impl as well * fix tests * Update .changes/tauri-bundler-nsis-args.md Co-authored-by: Lucas Fernandes Nogueira * Update .changes/tauri-updater-retain-args.md Co-authored-by: Lucas Fernandes Nogueira * more typos * fix update args * pull args from Env * check if not empty * pin memchr * Update core.rs * Update core.rs * move /args * fix build * lint * more lints --------- Co-authored-by: Lucas Fernandes Nogueira --- .changes/tauri-bundler-nsis-args.md | 5 + .changes/tauri-updater-retain-args.md | 5 + Cargo.lock | 1 + core/tauri-runtime-wry/src/system_tray.rs | 5 +- core/tauri-utils/src/config.rs | 3 + core/tauri/Cargo.toml | 1 + core/tauri/src/event.rs | 102 +++++++++--------- core/tauri/src/updater/core.rs | 51 ++++++--- .../bundle/windows/templates/installer.nsi | 3 +- 9 files changed, 106 insertions(+), 70 deletions(-) create mode 100644 .changes/tauri-bundler-nsis-args.md create mode 100644 .changes/tauri-updater-retain-args.md diff --git a/.changes/tauri-bundler-nsis-args.md b/.changes/tauri-bundler-nsis-args.md new file mode 100644 index 000000000..73d901e0e --- /dev/null +++ b/.changes/tauri-bundler-nsis-args.md @@ -0,0 +1,5 @@ +--- +'tauri-bundler': 'minor:feat' +--- + +On Windows, NSIS installer now supports `/ARGS` flag to pass arguments to be used when launching the app after installation, only works if `/R` is used. diff --git a/.changes/tauri-updater-retain-args.md b/.changes/tauri-updater-retain-args.md new file mode 100644 index 000000000..483cb3835 --- /dev/null +++ b/.changes/tauri-updater-retain-args.md @@ -0,0 +1,5 @@ +--- +'tauri': 'minor:enhance' +--- + +On Windows, retain command line args when relaunching the app after an update. Supports NSIS and WiX (without elevated update task). diff --git a/Cargo.lock b/Cargo.lock index 578f7948d..7d62264ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4030,6 +4030,7 @@ dependencies = [ "cocoa", "data-url", "dirs-next", + "dunce", "embed_plist", "encoding_rs", "flate2", diff --git a/core/tauri-runtime-wry/src/system_tray.rs b/core/tauri-runtime-wry/src/system_tray.rs index fb5511d43..08aba2e0c 100644 --- a/core/tauri-runtime-wry/src/system_tray.rs +++ b/core/tauri-runtime-wry/src/system_tray.rs @@ -3,10 +3,7 @@ // SPDX-License-Identifier: MIT pub use tauri_runtime::{ - menu::{ - Menu, MenuEntry, MenuItem, MenuUpdate, Submenu, SystemTrayMenu, SystemTrayMenuEntry, - SystemTrayMenuItem, TrayHandle, - }, + menu::{MenuUpdate, SystemTrayMenu, SystemTrayMenuEntry, SystemTrayMenuItem, TrayHandle}, Icon, SystemTrayEvent, }; use wry::application::event_loop::EventLoopWindowTarget; diff --git a/core/tauri-utils/src/config.rs b/core/tauri-utils/src/config.rs index 9e673c207..c9485884d 100644 --- a/core/tauri-utils/src/config.rs +++ b/core/tauri-utils/src/config.rs @@ -2586,6 +2586,9 @@ impl WindowsUpdateInstallMode { } /// Returns the associated nsis arguments. + /// + /// [WindowsUpdateInstallMode::Passive] will return `["/P", "/R"]` + /// [WindowsUpdateInstallMode::Quiet] will return `["/S", "/R"]` pub fn nsis_args(&self) -> &'static [&'static str] { match self { Self::Passive => &["/P", "/R"], diff --git a/core/tauri/Cargo.toml b/core/tauri/Cargo.toml index b5dbc76ca..516968ecd 100644 --- a/core/tauri/Cargo.toml +++ b/core/tauri/Cargo.toml @@ -112,6 +112,7 @@ cocoa = "0.24" objc = "0.2" [target."cfg(windows)".dependencies] +dunce = "1" webview2-com = "0.19.1" win7-notifications = { version = "0.4", optional = true } diff --git a/core/tauri/src/event.rs b/core/tauri/src/event.rs index 68d173f5a..06e9b715c 100644 --- a/core/tauri/src/event.rs +++ b/core/tauri/src/event.rs @@ -225,6 +225,57 @@ impl Listeners { } } +pub fn unlisten_js(listeners_object_name: String, event_name: String, event_id: u32) -> String { + format!( + " + (function () {{ + const listeners = (window['{listeners_object_name}'] || {{}})['{event_name}'] + if (listeners) {{ + const index = window['{listeners_object_name}']['{event_name}'].findIndex(e => e.id === {event_id}) + if (index > -1) {{ + window['{listeners_object_name}']['{event_name}'].splice(index, 1) + }} + }} + }})() + ", + ) +} + +pub fn listen_js( + listeners_object_name: String, + event: String, + event_id: u32, + window_label: Option, + handler: String, +) -> String { + format!( + " + (function () {{ + if (window['{listeners}'] === void 0) {{ + Object.defineProperty(window, '{listeners}', {{ value: Object.create(null) }}); + }} + if (window['{listeners}'][{event}] === void 0) {{ + Object.defineProperty(window['{listeners}'], {event}, {{ value: [] }}); + }} + const eventListeners = window['{listeners}'][{event}] + const listener = {{ + id: {event_id}, + windowLabel: {window_label}, + handler: {handler} + }}; + eventListeners.push(listener); + }})() + ", + listeners = listeners_object_name, + window_label = if let Some(l) = window_label { + crate::runtime::window::assert_label_is_valid(&l); + format!("'{l}'") + } else { + "null".to_owned() + }, + ) +} + #[cfg(test)] mod test { use super::*; @@ -298,54 +349,3 @@ mod test { } } } - -pub fn unlisten_js(listeners_object_name: String, event_name: String, event_id: u32) -> String { - format!( - " - (function () {{ - const listeners = (window['{listeners_object_name}'] || {{}})['{event_name}'] - if (listeners) {{ - const index = window['{listeners_object_name}']['{event_name}'].findIndex(e => e.id === {event_id}) - if (index > -1) {{ - window['{listeners_object_name}']['{event_name}'].splice(index, 1) - }} - }} - }})() - ", - ) -} - -pub fn listen_js( - listeners_object_name: String, - event: String, - event_id: u32, - window_label: Option, - handler: String, -) -> String { - format!( - " - (function () {{ - if (window['{listeners}'] === void 0) {{ - Object.defineProperty(window, '{listeners}', {{ value: Object.create(null) }}); - }} - if (window['{listeners}'][{event}] === void 0) {{ - Object.defineProperty(window['{listeners}'], {event}, {{ value: [] }}); - }} - const eventListeners = window['{listeners}'][{event}] - const listener = {{ - id: {event_id}, - windowLabel: {window_label}, - handler: {handler} - }}; - eventListeners.push(listener); - }})() - ", - listeners = listeners_object_name, - window_label = if let Some(l) = window_label { - crate::runtime::window::assert_label_is_valid(&l); - format!("'{l}'") - } else { - "null".to_owned() - }, - ) -} diff --git a/core/tauri/src/updater/core.rs b/core/tauri/src/updater/core.rs index 0fd703d5b..0f4830725 100644 --- a/core/tauri/src/updater/core.rs +++ b/core/tauri/src/updater/core.rs @@ -706,6 +706,7 @@ impl Update { &self.extract_path, self.with_elevated_task, &self.app.config(), + &self.app.env(), )?; #[cfg(not(target_os = "windows"))] copy_files_and_run(archive_buffer, &self.extract_path)?; @@ -805,6 +806,7 @@ fn copy_files_and_run( _extract_path: &Path, with_elevated_task: bool, config: &crate::Config, + env: &crate::Env, ) -> Result { // FIXME: We need to create a memory buffer with the MSI and then run it. // (instead of extracting the MSI to a temp path) @@ -830,6 +832,8 @@ fn copy_files_and_run( |p| format!("{p}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"), ); + let current_exe_args = env.args.clone(); + for path in paths { let found_path = path?.path(); // we support 2 type of files exe & msi for now @@ -842,29 +846,39 @@ fn copy_files_and_run( installer_path.push("\""); let installer_args = [ - config.tauri.updater.windows.install_mode.nsis_args(), + config + .tauri + .updater + .windows + .install_mode + .nsis_args() + .iter() + .map(ToString::to_string) + .collect(), + vec!["/ARGS".to_string()], + current_exe_args, config .tauri .updater .windows .installer_args .iter() - .map(AsRef::as_ref) - .collect::>() - .as_slice(), + .map(ToString::to_string) + .collect::>(), ] .concat(); // Run the EXE let mut cmd = Command::new(powershell_path); cmd - .args(["-NoProfile", "-WindowStyle", "Hidden"]) - .args(["Start-Process"]) + .args(["-NoProfile", "-WindowStyle", "Hidden", "Start-Process"]) .arg(installer_path); if !installer_args.is_empty() { cmd.arg("-ArgumentList").arg(installer_args.join(", ")); } - cmd.spawn().expect("installer failed to start"); + cmd + .spawn() + .expect("Running NSIS installer from powershell has failed to start"); exit(0); } else if found_path.extension() == Some(OsStr::new("msi")) { @@ -908,10 +922,10 @@ fn copy_files_and_run( } // we need to wrap the current exe path in quotes for Start-Process - let mut current_exe_arg = std::ffi::OsString::new(); - current_exe_arg.push("\""); - current_exe_arg.push(current_exe()?); - current_exe_arg.push("\""); + let mut current_executable = std::ffi::OsString::new(); + current_executable.push("\""); + current_executable.push(dunce::simplified(¤t_exe()?)); + current_executable.push("\""); let mut msi_path = std::ffi::OsString::new(); msi_path.push("\"\"\""); @@ -933,7 +947,9 @@ fn copy_files_and_run( .concat(); // run the installer and relaunch the application - let powershell_install_res = Command::new(powershell_path) + let mut powershell_cmd = Command::new(powershell_path); + + powershell_cmd .args(["-NoProfile", "-WindowStyle", "Hidden"]) .args([ "Start-Process", @@ -946,8 +962,15 @@ fn copy_files_and_run( .arg(&msi_path) .arg(format!(", {}, /promptrestart;", installer_args.join(", "))) .arg("Start-Process") - .arg(current_exe_arg) - .spawn(); + .arg(current_executable); + + if !current_exe_args.is_empty() { + powershell_cmd + .arg("-ArgumentList") + .arg(current_exe_args.join(", ")); + } + + let powershell_install_res = powershell_cmd.spawn(); if powershell_install_res.is_err() { // fallback to running msiexec directly - relaunch won't be available // we use this here in case powershell fails in an older machine somehow diff --git a/tooling/bundler/src/bundle/windows/templates/installer.nsi b/tooling/bundler/src/bundle/windows/templates/installer.nsi index 05aecc5db..a94e89ee7 100644 --- a/tooling/bundler/src/bundle/windows/templates/installer.nsi +++ b/tooling/bundler/src/bundle/windows/templates/installer.nsi @@ -606,7 +606,8 @@ Function .onInstSuccess check_r_flag: ${GetOptions} $CMDLINE "/R" $R0 IfErrors run_done 0 - Exec '"$INSTDIR\${MAINBINARYNAME}.exe"' + ${GetOptions} $CMDLINE "/ARGS" $R0 + Exec '"$INSTDIR\${MAINBINARYNAME}.exe" $R0' run_done: FunctionEnd