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::stdin;
use std::io::stdout; use std::io::stdout;
use std::io::Write; use std::io::Write;
use std::os::unix::process::CommandExt;
use std::process::Command; use std::process::Command;
use std::time::SystemTime; use std::time::SystemTime;
use std::time::UNIX_EPOCH; use std::time::UNIX_EPOCH;
@ -25,8 +24,8 @@ use anyhow::Result;
use crate::platform::ensure_terminal_shown; use crate::platform::ensure_terminal_shown;
use crate::platform::get_exe_and_resources_dirs; use crate::platform::get_exe_and_resources_dirs;
use crate::platform::get_uv_binary_name; use crate::platform::get_uv_binary_name;
use crate::platform::launch_anki_after_update;
use crate::platform::launch_anki_normally; use crate::platform::launch_anki_normally;
use crate::platform::respawn_launcher;
mod platform; mod platform;
@ -148,8 +147,8 @@ fn run() -> Result<()> {
println!("\x1B[1mYou can close this window.\x1B[0m\n"); println!("\x1B[1mYou can close this window.\x1B[0m\n");
} }
let cmd = build_python_command(&state.uv_install_root, &[])?; // respawn the launcher as a disconnected subprocess for normal startup
launch_anki_after_update(cmd)?; respawn_launcher()?;
Ok(()) 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); 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); cmd.args(args);
// tell the Python code it was invoked by the launcher, and updating is // tell the Python code it was invoked by the launcher, and updating is
// available // 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; 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()) .stdout(Stdio::null())
.stderr(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; use std::os::windows::process::CommandExt;
const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200; const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
const DETACHED_PROCESS: u32 = 0x00000008; 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; 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); std::mem::forget(child);
Ok(()) Ok(())