mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-08-25 21:04:04 +02:00
Fix macOS relaunch activation (#583)
This commit is contained in:
@@ -68,6 +68,7 @@ mod notifications;
|
|||||||
mod plugin_events;
|
mod plugin_events;
|
||||||
mod plugins_ext;
|
mod plugins_ext;
|
||||||
mod render;
|
mod render;
|
||||||
|
mod restart;
|
||||||
mod rpc_ext;
|
mod rpc_ext;
|
||||||
mod sync_ext;
|
mod sync_ext;
|
||||||
mod updates;
|
mod updates;
|
||||||
@@ -904,7 +905,7 @@ async fn cmd_grpc_go<R: Runtime>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn cmd_restart<R: Runtime>(app_handle: AppHandle<R>) -> YaakResult<()> {
|
async fn cmd_restart<R: Runtime>(app_handle: AppHandle<R>) -> YaakResult<()> {
|
||||||
app_handle.request_restart();
|
restart::request_restart(&app_handle);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1408,6 +1409,7 @@ pub fn run() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
RunEvent::Exit => restart::relaunch_if_requested(),
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
use log::{error, info};
|
||||||
|
#[cfg(any(target_os = "macos", test))]
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use tauri::{AppHandle, Runtime};
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
static RELAUNCH_WITH_LAUNCH_SERVICES: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
|
/// Restart the app without directly spawning the executable on macOS.
|
||||||
|
///
|
||||||
|
/// Tauri's current macOS restart path starts the executable from the dying
|
||||||
|
/// process. Besides inheriting stale process state, that bypasses
|
||||||
|
/// LaunchServices and can leave the replacement app running without an active
|
||||||
|
/// window. Defer the relaunch until `RunEvent::Exit`, when the event loop is
|
||||||
|
/// already shutting down, and hand it to LaunchServices instead.
|
||||||
|
pub fn request_restart<R: Runtime>(app_handle: &AppHandle<R>) {
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
if current_app_bundle().is_some() {
|
||||||
|
info!("Requesting restart through macOS LaunchServices");
|
||||||
|
RELAUNCH_WITH_LAUNCH_SERVICES.store(true, Ordering::SeqCst);
|
||||||
|
app_handle.exit(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
app_handle.request_restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Complete a pending macOS restart after Tauri has emitted its exit events.
|
||||||
|
pub fn relaunch_if_requested() {
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
if !RELAUNCH_WITH_LAUNCH_SERVICES.swap(false, Ordering::SeqCst) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(bundle) = current_app_bundle() else {
|
||||||
|
error!("Failed to resolve the app bundle for restart");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
match Command::new("/usr/bin/open")
|
||||||
|
.arg("-n")
|
||||||
|
.arg(&bundle)
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.stderr(Stdio::null())
|
||||||
|
.spawn()
|
||||||
|
{
|
||||||
|
Ok(_) => info!("Relaunching {} through LaunchServices", bundle.display()),
|
||||||
|
Err(error) => error!("Failed to relaunch through LaunchServices: {error}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
fn current_app_bundle() -> Option<PathBuf> {
|
||||||
|
app_bundle_from_executable(&std::env::current_exe().ok()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "macos", test))]
|
||||||
|
fn app_bundle_from_executable(executable: &Path) -> Option<PathBuf> {
|
||||||
|
let macos_dir = executable.parent()?;
|
||||||
|
if macos_dir.file_name()? != "MacOS" {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let contents_dir = macos_dir.parent()?;
|
||||||
|
if contents_dir.file_name()? != "Contents" {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let bundle = contents_dir.parent()?;
|
||||||
|
if bundle.extension()? != "app" {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(bundle.to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::app_bundle_from_executable;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resolves_macos_app_bundle() {
|
||||||
|
assert_eq!(
|
||||||
|
app_bundle_from_executable(Path::new(
|
||||||
|
"/Applications/Yaak.app/Contents/MacOS/yaak-app-client"
|
||||||
|
)),
|
||||||
|
Some(PathBuf::from("/Applications/Yaak.app"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ignores_unbundled_executable() {
|
||||||
|
assert_eq!(
|
||||||
|
app_bundle_from_executable(Path::new("/workspace/target/debug/yaak-app-client")),
|
||||||
|
None
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ use std::time::{Duration, Instant};
|
|||||||
|
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::models_ext::QueryManagerExt;
|
use crate::models_ext::QueryManagerExt;
|
||||||
|
use crate::restart;
|
||||||
use log::{debug, error, info, warn};
|
use log::{debug, error, info, warn};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tauri::{Emitter, Listener, Manager, Runtime, WebviewWindow};
|
use tauri::{Emitter, Listener, Manager, Runtime, WebviewWindow};
|
||||||
@@ -332,7 +333,7 @@ async fn start_native_update<R: Runtime>(window: &WebviewWindow<R>, update: &Upd
|
|||||||
))
|
))
|
||||||
.blocking_show()
|
.blocking_show()
|
||||||
{
|
{
|
||||||
window.app_handle().request_restart();
|
restart::request_restart(window.app_handle());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user