Skip to content

Commit

Permalink
feat: retain cli args when relaunching after update, closes #7402 (#7718
Browse files Browse the repository at this point in the history
)

* 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>
  • Loading branch information
amrbashir and lucasfernog authored Jan 31, 2024
1 parent 0bff8c3 commit 8ce51ce
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 70 deletions.
5 changes: 5 additions & 0 deletions .changes/tauri-bundler-nsis-args.md
Original file line number Diff line number Diff line change
@@ -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.
5 changes: 5 additions & 0 deletions .changes/tauri-updater-retain-args.md
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 1 addition & 4 deletions core/tauri-runtime-wry/src/system_tray.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions core/tauri-utils/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down
1 change: 1 addition & 0 deletions core/tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }

Expand Down
102 changes: 51 additions & 51 deletions core/tauri/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down Expand Up @@ -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()
},
)
}
51 changes: 37 additions & 14 deletions core/tauri/src/updater/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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")) {
Expand Down Expand Up @@ -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("\"\"\"");
Expand All @@ -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",
Expand All @@ -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
Expand Down
3 changes: 2 additions & 1 deletion tooling/bundler/src/bundle/windows/templates/installer.nsi
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit 8ce51ce

Please sign in to comment.