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 <lucas@tauri.studio>

* Update .changes/tauri-updater-retain-args.md

Co-authored-by: Lucas Fernandes Nogueira <lucas@tauri.studio>

* 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 <lucas@tauri.studio>
This commit is contained in:
Amr Bashir 2024-01-31 21:02:48 +02:00 committed by GitHub
parent 0bff8c325d
commit 8ce51cec3b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 106 additions and 70 deletions

View File

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

View File

@ -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).

1
Cargo.lock generated
View File

@ -4030,6 +4030,7 @@ dependencies = [
"cocoa",
"data-url",
"dirs-next",
"dunce",
"embed_plist",
"encoding_rs",
"flate2",

View File

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

View File

@ -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"],

View File

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

View File

@ -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<String>,
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<String>,
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()
},
)
}

View File

@ -706,6 +706,7 @@ impl<R: Runtime> Update<R> {
&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<R: Read + Seek>(
_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<R: Read + Seek>(
|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<R: Read + Seek>(
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::<Vec<_>>()
.as_slice(),
.map(ToString::to_string)
.collect::<Vec<_>>(),
]
.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<R: Read + Seek>(
}
// 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(&current_exe()?));
current_executable.push("\"");
let mut msi_path = std::ffi::OsString::new();
msi_path.push("\"\"\"");
@ -933,7 +947,9 @@ fn copy_files_and_run<R: Read + Seek>(
.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<R: Read + Seek>(
.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

View File

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