From 5be9dc1781ef6d2cbfbdbcd417edd0935cbb83a8 Mon Sep 17 00:00:00 2001 From: koh-sh <34917718+koh-sh@users.noreply.github.com> Date: Thu, 9 Apr 2026 14:14:54 +0900 Subject: [PATCH] Fix duplicate window when opening from CLI on macOS (#48146) Closes #47140 Closes #44691 When launching Zed from the CLI (`zed .`), macOS delivers the path as a `kAEGetURL` Apple Event via Launch Services. Zed relied on the `application:openURLs:` delegate method to receive this, but its timing relative to `applicationDidFinishLaunching:` is not guaranteed by macOS. In release builds, the app reaches `didFinishLaunching` before the URL is delivered, causing it to be missed and a duplicate window to be opened. This does not reproduce in debug builds due to slower initialization, which is why the issue was hard to reproduce from source. This replaces `application:openURLs:` with a custom `kAEGetURL` Apple Event handler registered in `applicationWillFinishLaunching:`. Apple Events are guaranteed to be delivered synchronously between `willFinishLaunching` and `didFinishLaunching`, ensuring the URL is always available before startup logic runs. Release Notes: - Fixed duplicate window creation when opening files/directories from the CLI on macOS. Co-authored-by: Conrad Irwin --- crates/gpui_macos/src/platform.rs | 69 ++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 23 deletions(-) diff --git a/crates/gpui_macos/src/platform.rs b/crates/gpui_macos/src/platform.rs index 5bae3cfb6aa73c99038e0017332a046035dc1589..291eec54df43459e8ee15cd35d73a9dfd6e4dd15 100644 --- a/crates/gpui_macos/src/platform.rs +++ b/crates/gpui_macos/src/platform.rs @@ -62,6 +62,9 @@ use util::{ const NSUTF8StringEncoding: NSUInteger = 4; const MAC_PLATFORM_IVAR: &str = "platform"; +const INTERNET_EVENT_CLASS: u32 = 0x4755524c; // 'GURL' +const AE_GET_URL: u32 = 0x4755524c; // 'GURL' +const DIRECT_OBJECT_KEY: u32 = 0x2d2d2d2d; // '----' static mut APP_CLASS: *const Class = ptr::null(); static mut APP_DELEGATE_CLASS: *const Class = ptr::null(); @@ -136,8 +139,8 @@ unsafe fn build_classes() { handle_dock_menu as extern "C" fn(&mut Object, Sel, id) -> id, ); decl.add_method( - sel!(application:openURLs:), - open_urls as extern "C" fn(&mut Object, Sel, id, id), + sel!(handleGetURLEvent:withReplyEvent:), + handle_get_url_event as extern "C" fn(&mut Object, Sel, id, id), ); decl.add_method( @@ -1178,7 +1181,7 @@ unsafe fn get_mac_platform(object: &mut Object) -> &MacPlatform { } } -extern "C" fn will_finish_launching(_this: &mut Object, _: Sel, _: id) { +extern "C" fn will_finish_launching(this: &mut Object, _: Sel, _: id) { unsafe { let user_defaults: id = msg_send![class!(NSUserDefaults), standardUserDefaults]; @@ -1192,6 +1195,17 @@ extern "C" fn will_finish_launching(_this: &mut Object, _: Sel, _: id) { let false_value: id = msg_send![class!(NSNumber), numberWithBool:false]; let _: () = msg_send![user_defaults, setObject: false_value forKey: name]; } + + // Register kAEGetURL Apple Event handler so that URL open requests are + // delivered synchronously before applicationDidFinishLaunching:, replacing + // application:openURLs: which has non-deterministic timing. + let event_manager: id = msg_send![class!(NSAppleEventManager), sharedAppleEventManager]; + let _: () = msg_send![event_manager, + setEventHandler: this as id + andSelector: sel!(handleGetURLEvent:withReplyEvent:) + forEventClass: INTERNET_EVENT_CLASS + andEventID: AE_GET_URL + ]; } } @@ -1288,27 +1302,36 @@ extern "C" fn on_thermal_state_change(this: &mut Object, _: Sel, _: id) { } } -extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) { - let urls = unsafe { - (0..urls.count()) - .filter_map(|i| { - let url = urls.objectAtIndex(i); - match CStr::from_ptr(url.absoluteString().UTF8String() as *mut c_char).to_str() { - Ok(string) => Some(string.to_string()), - Err(err) => { - log::error!("error converting path to string: {}", err); - None - } +extern "C" fn handle_get_url_event(this: &mut Object, _: Sel, event: id, _reply: id) { + unsafe { + let descriptor: id = msg_send![event, paramDescriptorForKeyword: DIRECT_OBJECT_KEY]; + if descriptor == nil { + return; + } + let url_string: id = msg_send![descriptor, stringValue]; + if url_string == nil { + return; + } + let utf8_ptr = url_string.UTF8String() as *mut c_char; + if utf8_ptr.is_null() { + return; + } + let url_str = CStr::from_ptr(utf8_ptr); + match url_str.to_str() { + Ok(string) => { + let urls = vec![string.to_string()]; + let platform = get_mac_platform(this); + let mut lock = platform.0.lock(); + if let Some(mut callback) = lock.open_urls.take() { + drop(lock); + callback(urls); + platform.0.lock().open_urls.get_or_insert(callback); } - }) - .collect::>() - }; - let platform = unsafe { get_mac_platform(this) }; - let mut lock = platform.0.lock(); - if let Some(mut callback) = lock.open_urls.take() { - drop(lock); - callback(urls); - platform.0.lock().open_urls.get_or_insert(callback); + } + Err(err) => { + log::error!("error converting URL to string: {}", err); + } + } } }