Anki/qt/launcher/src/platform/mod.rs
Damien Elmes 8932199513 Possible fix for launcher failing to appear on some Linux systems
While testing the previous PR, I noticed that if stdout is set to None,
the same behaviour is shown as in the following report:
https://forums.ankiweb.net/t/cannot-switch-versions/64565

This leads me to wonder whether IsTerminal is behaving differently
on that user's system, and the use of an env var may be more reliable.
2025-07-28 21:53:44 +10:00

138 lines
3.8 KiB
Rust

// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
#[cfg(all(unix, not(target_os = "macos")))]
pub mod unix;
#[cfg(target_os = "macos")]
pub mod mac;
#[cfg(target_os = "windows")]
pub mod windows;
use std::path::PathBuf;
use anki_process::CommandExt;
use anyhow::Context;
use anyhow::Result;
pub fn get_exe_and_resources_dirs() -> Result<(PathBuf, PathBuf)> {
let exe_dir = std::env::current_exe()
.context("Failed to get current executable path")?
.parent()
.context("Failed to get executable directory")?
.to_owned();
let resources_dir = if cfg!(target_os = "macos") {
// On macOS, resources are in ../Resources relative to the executable
exe_dir
.parent()
.context("Failed to get parent directory")?
.join("Resources")
} else {
// On other platforms, resources are in the same directory as executable
exe_dir.clone()
};
Ok((exe_dir, resources_dir))
}
pub fn get_uv_binary_name() -> &'static str {
if cfg!(target_os = "windows") {
"uv.exe"
} else if cfg!(target_os = "macos") {
"uv"
} else if cfg!(target_arch = "x86_64") {
"uv.amd64"
} else {
"uv.arm64"
}
}
pub fn respawn_launcher() -> Result<()> {
use std::process::Stdio;
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());
#[cfg(windows)]
{
use std::os::windows::process::CommandExt;
const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
const DETACHED_PROCESS: u32 = 0x00000008;
launcher_cmd.creation_flags(CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS);
}
#[cfg(all(unix, not(target_os = "macos")))]
{
use std::os::unix::process::CommandExt;
launcher_cmd.process_group(0);
}
let child = launcher_cmd.ensure_spawn()?;
std::mem::forget(child);
Ok(())
}
pub fn launch_anki_normally(mut cmd: std::process::Command) -> Result<()> {
#[cfg(windows)]
{
crate::platform::windows::prepare_to_launch_normally();
cmd.ensure_success()?;
}
#[cfg(unix)]
cmd.ensure_exec()?;
Ok(())
}
#[cfg(windows)]
pub use windows::ensure_terminal_shown;
#[cfg(unix)]
pub fn ensure_terminal_shown() -> Result<()> {
use std::io::IsTerminal;
let want_terminal = std::env::var("ANKI_LAUNCHER_WANT_TERMINAL").is_ok();
let stdout_is_terminal = IsTerminal::is_terminal(&std::io::stdout());
if want_terminal || !stdout_is_terminal {
#[cfg(target_os = "macos")]
mac::relaunch_in_terminal()?;
#[cfg(not(target_os = "macos"))]
unix::relaunch_in_terminal()?;
}
// Set terminal title to "Anki Launcher"
print!("\x1b]2;Anki Launcher\x07");
Ok(())
}
pub fn ensure_os_supported() -> Result<()> {
#[cfg(all(unix, not(target_os = "macos")))]
unix::ensure_glibc_supported()?;
Ok(())
}