mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-07 10:16:03 -10:00
fix(app): keep the pinned Dock icon after an update on macOS (#1906)
The updater swaps the whole app bundle, and tauri::process::restart then opens the new bundle with `open -n` while the old process is still alive, so the Dock gives the new instance its own tile and leaves the pinned one stopped. Restart on macOS now hands the relaunch to a detached shell that waits for this process to exit before opening the bundle. This covers every restart path (updates and Invalidate Caches › Restart); Windows and Linux keep Tauri's restart.
This commit is contained in:
1 parent
7e7da950fc
commit
7a0c9354fa
2 files changed
+93
No files matched your search
@@ -18,6 +18,8 @@ mod logging;
|
||||
mod macos;
|
||||
mod menu;
|
||||
mod plugin_web;
|
||||
#[cfg(target_os = "macos")]
|
||||
mod relaunch;
|
||||
mod remote_dev;
|
||||
mod remote_wsl;
|
||||
|
||||
@@ -248,6 +250,9 @@ pub fn run() {
|
||||
}
|
||||
// Cleanup must finish before update/restart replaces this process.
|
||||
if app.try_state::<WindowsState>().is_some_and(|w| w.restart_requested()) {
|
||||
#[cfg(target_os = "macos")]
|
||||
relaunch::restart(&app.env());
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
tauri::process::restart(&app.env());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
//! Restarting the app on macOS once this process has exited.
|
||||
//!
|
||||
//! `tauri::process::restart` launches the new instance while this one is still running.
|
||||
//! After an update has replaced the app bundle, the Dock then gives the new instance a
|
||||
//! tile of its own instead of the pinned one. A detached shell waits for this process
|
||||
//! to exit and only then opens the bundle.
|
||||
|
||||
use std::ffi::OsString;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
const WAIT_THEN_OPEN: &str = r#"pid=$1; opener=$2; app=$3; shift 3; while kill -0 "$pid" 2>/dev/null; do sleep 0.1; done; exec "$opener" -n "$app" --args "$@""#;
|
||||
|
||||
/// Restarts the app; never returns.
|
||||
pub fn restart(env: &tauri::Env) -> ! {
|
||||
if let Some(bundle) = tauri::process::current_binary(env).ok().and_then(|bin| app_bundle(&bin)) {
|
||||
let args = env.args_os.iter().skip(1).cloned().collect::<Vec<_>>();
|
||||
match wait_then_open(std::process::id(), Path::new("/usr/bin/open"), &bundle, &args).spawn() {
|
||||
Ok(_) => std::process::exit(0),
|
||||
Err(e) => tracing::error!("failed to schedule the restart: {e}"),
|
||||
}
|
||||
}
|
||||
tauri::process::restart(env)
|
||||
}
|
||||
|
||||
/// The `.app` bundle holding `binary`, when it sits at `<bundle>/Contents/MacOS/<name>`.
|
||||
fn app_bundle(binary: &Path) -> Option<PathBuf> {
|
||||
let macos = binary.parent()?;
|
||||
let contents = macos.parent()?;
|
||||
if macos.file_name()? != "MacOS" || contents.file_name()? != "Contents" {
|
||||
return None;
|
||||
}
|
||||
contents.parent().map(Path::to_path_buf)
|
||||
}
|
||||
|
||||
fn wait_then_open(pid: u32, opener: &Path, bundle: &Path, args: &[OsString]) -> Command {
|
||||
use std::os::unix::process::CommandExt;
|
||||
let mut command = Command::new("/bin/sh");
|
||||
command
|
||||
.arg("-c")
|
||||
.arg(WAIT_THEN_OPEN)
|
||||
.arg("openidea-restart")
|
||||
.arg(pid.to_string())
|
||||
.arg(opener)
|
||||
.arg(bundle)
|
||||
.args(args)
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.process_group(0);
|
||||
command
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
#[test]
|
||||
fn finds_the_bundle_of_its_binary() {
|
||||
assert_eq!(
|
||||
app_bundle(Path::new("/Applications/OpenIDEA.app/Contents/MacOS/openidea")),
|
||||
Some(PathBuf::from("/Applications/OpenIDEA.app"))
|
||||
);
|
||||
assert_eq!(app_bundle(Path::new("/repo/target/debug/openidea")), None);
|
||||
assert_eq!(app_bundle(Path::new("/x/MacOS/openidea")), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opens_the_bundle_only_after_the_process_exits() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let log = dir.path().join("opened");
|
||||
let opener = dir.path().join("opener");
|
||||
std::fs::write(&opener, format!("#!/bin/sh\nprintf '%s|' \"$@\" > '{}'\n", log.display())).unwrap();
|
||||
std::fs::set_permissions(&opener, std::os::unix::fs::PermissionsExt::from_mode(0o755)).unwrap();
|
||||
|
||||
let mut running = Command::new("/bin/sleep").arg("1").spawn().unwrap();
|
||||
let pid = running.id();
|
||||
let reaper = std::thread::spawn(move || running.wait());
|
||||
let args = [OsString::from("--flag"), OsString::from("a b")];
|
||||
let started = Instant::now();
|
||||
wait_then_open(pid, &opener, Path::new("/Apps/X.app"), &args).status().unwrap();
|
||||
|
||||
assert!(started.elapsed() >= Duration::from_millis(700), "opened before the process exited");
|
||||
assert!(reaper.join().unwrap().unwrap().success());
|
||||
assert_eq!(std::fs::read_to_string(&log).unwrap(), "-n|/Apps/X.app|--args|--flag|a b|");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user