Fix duplicate window when opening from CLI on macOS (#48146)

koh-sh and Conrad Irwin created

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 <conrad.irwin@gmail.com>

Change summary

crates/gpui_macos/src/platform.rs | 69 ++++++++++++++++++++++-----------
1 file changed, 46 insertions(+), 23 deletions(-)

Detailed changes

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::<Vec<_>>()
-    };
-    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);
+            }
+        }
     }
 }