From ada465ac3c3b67a460856a0c5535f22cd0320837 Mon Sep 17 00:00:00 2001 From: Ghibli1024 Date: Fri, 10 Jul 2026 03:05:20 +0800 Subject: [PATCH] Support ChatGPT app launch fallback on macOS --- crates/codex-plus-core/src/app_paths.rs | 18 +++++-- crates/codex-plus-core/tests/launcher.rs | 61 ++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 4 deletions(-) diff --git a/crates/codex-plus-core/src/app_paths.rs b/crates/codex-plus-core/src/app_paths.rs index eea428576..de9369da0 100644 --- a/crates/codex-plus-core/src/app_paths.rs +++ b/crates/codex-plus-core/src/app_paths.rs @@ -223,7 +223,13 @@ pub fn normalize_codex_app_path(path: &Path) -> Option { pub fn build_codex_executable(app_dir: &Path) -> PathBuf { if app_dir.extension() == Some(OsStr::new("app")) { - return app_dir.join("Contents").join("MacOS").join("Codex"); + let macos_dir = app_dir.join("Contents").join("MacOS"); + if let Some(executable) = macos_app_plist_value(app_dir, "CFBundleExecutable") + .filter(|value| !value.contains('/') && !value.contains('\\')) + { + return macos_dir.join(executable); + } + return macos_dir.join("Codex"); } let upper = app_dir.join("Codex.exe"); if upper.exists() { @@ -283,9 +289,13 @@ fn codex_package_version(package_dir: &Path) -> Option { } fn macos_app_version(app_dir: &Path) -> Option { + macos_app_plist_value(app_dir, "CFBundleShortVersionString") + .or_else(|| macos_app_plist_value(app_dir, "CFBundleVersion")) +} + +fn macos_app_plist_value(app_dir: &Path, key: &str) -> Option { let plist = std::fs::read_to_string(app_dir.join("Contents").join("Info.plist")).ok()?; - plist_string_value(&plist, "CFBundleShortVersionString") - .or_else(|| plist_string_value(&plist, "CFBundleVersion")) + plist_string_value(&plist, key) } fn plist_string_value(plist: &str, key: &str) -> Option { @@ -310,7 +320,7 @@ fn macos_app_candidates(root: &Path) -> Vec { if root.extension() == Some(OsStr::new("app")) { return vec![root.to_path_buf()]; } - ["Codex.app", "OpenAI Codex.app", "OpenAI.Codex.app"] + ["Codex.app", "OpenAI Codex.app", "OpenAI.Codex.app", "ChatGPT.app"] .into_iter() .map(|name| root.join(name)) .collect() diff --git a/crates/codex-plus-core/tests/launcher.rs b/crates/codex-plus-core/tests/launcher.rs index d2df5f0b7..866d59e58 100644 --- a/crates/codex-plus-core/tests/launcher.rs +++ b/crates/codex-plus-core/tests/launcher.rs @@ -154,6 +154,36 @@ fn app_paths_find_macos_codex_app_prefers_first_search_root_and_known_names() { ); } +#[test] +fn app_paths_prefers_codex_app_over_chatgpt_app() { + let temp = tempfile::tempdir().unwrap(); + let root = temp.path().join("Applications"); + let codex = root.join("Codex.app"); + let chatgpt = root.join("ChatGPT.app"); + std::fs::create_dir_all(&codex).unwrap(); + std::fs::create_dir_all(&chatgpt).unwrap(); + + assert_eq!( + find_macos_codex_app(&[root]).as_deref(), + Some(codex.as_path()) + ); +} + +#[test] +fn app_paths_preserves_legacy_macos_candidates_before_chatgpt_app() { + let temp = tempfile::tempdir().unwrap(); + let root = temp.path().join("Applications"); + let legacy = root.join("OpenAI Codex.app"); + let chatgpt = root.join("ChatGPT.app"); + std::fs::create_dir_all(&legacy).unwrap(); + std::fs::create_dir_all(&chatgpt).unwrap(); + + assert_eq!( + find_macos_codex_app(&[root]).as_deref(), + Some(legacy.as_path()) + ); +} + #[test] fn app_paths_build_macos_bundle_executable() { let app = PathBuf::from("/Applications/OpenAI Codex.app"); @@ -164,6 +194,37 @@ fn app_paths_build_macos_bundle_executable() { ); } +#[test] +fn app_paths_finds_chatgpt_bundle_and_uses_its_declared_executable() { + let temp = tempfile::tempdir().unwrap(); + let root = temp.path().join("Applications"); + let app = root.join("ChatGPT.app"); + let contents = app.join("Contents"); + let macos = contents.join("MacOS"); + std::fs::create_dir_all(&macos).unwrap(); + std::fs::write( + contents.join("Info.plist"), + r#" + + + CFBundleIdentifier + com.openai.codex + CFBundleExecutable + ChatGPT + + +"#, + ) + .unwrap(); + std::fs::write(macos.join("ChatGPT"), "").unwrap(); + + assert_eq!( + find_macos_codex_app(&[root]).as_deref(), + Some(app.as_path()) + ); + assert_eq!(build_codex_executable(&app), macos.join("ChatGPT")); +} + #[test] fn app_paths_normalizes_executable_and_package_paths() { let temp = tempfile::tempdir().unwrap();