Fix icon/menu entries on macOS after update

We need to exec() Python from a GUI context so that the app name and
icon are inherited from our launcher bundle. And we need to munge
sys.argv[0] prior to main window instantiation so that we don't get
app menu entries like "Hide -c". We do that in the launcher instead
of __init__.py so that older versions display correctly too.
This commit is contained in:
Damien Elmes 2025-06-28 17:24:15 +07:00
parent f5073b402a
commit aa8dfe1cf4
2 changed files with 32 additions and 11 deletions

View file

@ -6,7 +6,6 @@
use std::io::stdin;
use std::io::stdout;
use std::io::Write;
use std::os::unix::process::CommandExt;
use std::process::Command;
use std::time::SystemTime;
use std::time::UNIX_EPOCH;
@ -25,8 +24,8 @@ use anyhow::Result;
use crate::platform::ensure_terminal_shown;
use crate::platform::get_exe_and_resources_dirs;
use crate::platform::get_uv_binary_name;
use crate::platform::launch_anki_after_update;
use crate::platform::launch_anki_normally;
use crate::platform::respawn_launcher;
mod platform;
@ -148,8 +147,8 @@ fn run() -> Result<()> {
println!("\x1B[1mYou can close this window.\x1B[0m\n");
}
let cmd = build_python_command(&state.uv_install_root, &[])?;
launch_anki_after_update(cmd)?;
// respawn the launcher as a disconnected subprocess for normal startup
respawn_launcher()?;
Ok(())
}
@ -493,7 +492,7 @@ fn build_python_command(uv_install_root: &std::path::Path, args: &[String]) -> R
};
let mut cmd = Command::new(python_exe);
cmd.args(["-c", "import aqt; aqt.run()"]);
cmd.args(["-c", "import aqt, sys; sys.argv[0] = 'Anki'; aqt.run()"]);
cmd.args(args);
// tell the Python code it was invoked by the launcher, and updating is
// available

View file

@ -49,10 +49,32 @@ pub fn get_uv_binary_name() -> &'static str {
}
}
pub fn launch_anki_after_update(mut cmd: std::process::Command) -> Result<()> {
pub fn respawn_launcher() -> Result<()> {
use std::process::Stdio;
cmd.stdin(Stdio::null())
let mut launcher_cmd = if cfg!(target_os = "macos") {
// On macOS, we need to launch the .app bundle, not the executable directly
let current_exe =
std::env::current_exe().context("Failed to get current executable path")?;
// Navigate from Contents/MacOS/launcher to the .app bundle
let app_bundle = current_exe
.parent() // MacOS
.and_then(|p| p.parent()) // Contents
.and_then(|p| p.parent()) // .app
.context("Failed to find .app bundle")?;
let mut cmd = std::process::Command::new("open");
cmd.arg(app_bundle);
cmd
} else {
let current_exe =
std::env::current_exe().context("Failed to get current executable path")?;
std::process::Command::new(current_exe)
};
launcher_cmd
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null());
@ -61,16 +83,16 @@ pub fn launch_anki_after_update(mut cmd: std::process::Command) -> Result<()> {
use std::os::windows::process::CommandExt;
const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
const DETACHED_PROCESS: u32 = 0x00000008;
cmd.creation_flags(CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS);
launcher_cmd.creation_flags(CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS);
}
#[cfg(unix)]
#[cfg(all(unix, not(target_os = "macos")))]
{
use std::os::unix::process::CommandExt;
cmd.process_group(0);
launcher_cmd.process_group(0);
}
let child = cmd.ensure_spawn()?;
let child = launcher_cmd.ensure_spawn()?;
std::mem::forget(child);
Ok(())