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.
Unified · Split
| @@ -62,6 +62,9 @@ use util::{ · 61 unmodified lines | ||
| 62 | 62 | const NSUTF8StringEncoding: NSUInteger = 4; |
| 63 | 63 | |
| 64 | 64 | const MAC_PLATFORM_IVAR: &str = "platform"; |
| 65 | +const INTERNET_EVENT_CLASS: u32 = 0x4755524c; // 'GURL' | |
| 66 | +const AE_GET_URL: u32 = 0x4755524c; // 'GURL' | |
| 67 | +const DIRECT_OBJECT_KEY: u32 = 0x2d2d2d2d; // '----' | |
| 65 | 68 | static mut APP_CLASS: *const Class = ptr::null(); |
| 66 | 69 | static mut APP_DELEGATE_CLASS: *const Class = ptr::null(); |
| 67 | 70 | |
| @@ -136,8 +139,8 @@ unsafe fn build_classes() { · 68 unmodified lines | ||
| 136 | 139 | handle_dock_menu as extern "C" fn(&mut Object, Sel, id) -> id, |
| 137 | 140 | ); |
| 138 | 141 | decl.add_method( |
| 139 | − sel!(application:openURLs:), | |
| 140 | − open_urls as extern "C" fn(&mut Object, Sel, id, id), | |
| 142 | + sel!(handleGetURLEvent:withReplyEvent:), | |
| 143 | + handle_get_url_event as extern "C" fn(&mut Object, Sel, id, id), | |
| 141 | 144 | ); |
| 142 | 145 | |
| 143 | 146 | decl.add_method( |
| @@ -1178,7 +1181,7 @@ unsafe fn get_mac_platform(object: &mut Object) -> &MacPlatform { · 1034 unmodified lines | ||
| 1178 | 1181 | } |
| 1179 | 1182 | } |
| 1180 | 1183 | |
| 1181 | −extern "C" fn will_finish_launching(_this: &mut Object, _: Sel, _: id) { | |
| 1184 | +extern "C" fn will_finish_launching(this: &mut Object, _: Sel, _: id) { | |
| 1182 | 1185 | unsafe { |
| 1183 | 1186 | let user_defaults: id = msg_send![class!(NSUserDefaults), standardUserDefaults]; |
| 1184 | 1187 | |
| @@ -1192,6 +1195,17 @@ extern "C" fn will_finish_launching(_this: &mut Object, _: Sel, _: id) { · 7 unmodified lines | ||
| 1192 | 1195 | let false_value: id = msg_send![class!(NSNumber), numberWithBool:false]; |
| 1193 | 1196 | let _: () = msg_send![user_defaults, setObject: false_value forKey: name]; |
| 1194 | 1197 | } |
| 1198 | + | |
| 1199 | + // Register kAEGetURL Apple Event handler so that URL open requests are | |
| 1200 | + // delivered synchronously before applicationDidFinishLaunching:, replacing | |
| 1201 | + // application:openURLs: which has non-deterministic timing. | |
| 1202 | + let event_manager: id = msg_send![class!(NSAppleEventManager), sharedAppleEventManager]; | |
| 1203 | + let _: () = msg_send![event_manager, | |
| 1204 | + setEventHandler: this as id | |
| 1205 | + andSelector: sel!(handleGetURLEvent:withReplyEvent:) | |
| 1206 | + forEventClass: INTERNET_EVENT_CLASS | |
| 1207 | + andEventID: AE_GET_URL | |
| 1208 | + ]; | |
| 1195 | 1209 | } |
| 1196 | 1210 | } |
| 1197 | 1211 | |
| @@ -1288,27 +1302,36 @@ extern "C" fn on_thermal_state_change(this: &mut Object, _: Sel, _: id) { · 90 unmodified lines | ||
| 1288 | 1302 | } |
| 1289 | 1303 | } |
| 1290 | 1304 | |
| 1291 | −extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) { | |
| 1292 | − let urls = unsafe { | |
| 1293 | − (0..urls.count()) | |
| 1294 | − .filter_map(|i| { | |
| 1295 | − let url = urls.objectAtIndex(i); | |
| 1296 | − match CStr::from_ptr(url.absoluteString().UTF8String() as *mut c_char).to_str() { | |
| 1297 | − Ok(string) => Some(string.to_string()), | |
| 1298 | − Err(err) => { | |
| 1299 | − log::error!("error converting path to string: {}", err); | |
| 1300 | − None | |
| 1301 | − } | |
| 1305 | +extern "C" fn handle_get_url_event(this: &mut Object, _: Sel, event: id, _reply: id) { | |
| 1306 | + unsafe { | |
| 1307 | + let descriptor: id = msg_send![event, paramDescriptorForKeyword: DIRECT_OBJECT_KEY]; | |
| 1308 | + if descriptor == nil { | |
| 1309 | + return; | |
| 1310 | + } | |
| 1311 | + let url_string: id = msg_send![descriptor, stringValue]; | |
| 1312 | + if url_string == nil { | |
| 1313 | + return; | |
| 1314 | + } | |
| 1315 | + let utf8_ptr = url_string.UTF8String() as *mut c_char; | |
| 1316 | + if utf8_ptr.is_null() { | |
| 1317 | + return; | |
| 1318 | + } | |
| 1319 | + let url_str = CStr::from_ptr(utf8_ptr); | |
| 1320 | + match url_str.to_str() { | |
| 1321 | + Ok(string) => { | |
| 1322 | + let urls = vec![string.to_string()]; | |
| 1323 | + let platform = get_mac_platform(this); | |
| 1324 | + let mut lock = platform.0.lock(); | |
| 1325 | + if let Some(mut callback) = lock.open_urls.take() { | |
| 1326 | + drop(lock); | |
| 1327 | + callback(urls); | |
| 1328 | + platform.0.lock().open_urls.get_or_insert(callback); | |
| 1302 | 1329 | } |
| 1303 | − }) | |
| 1304 | − .collect::<Vec<_>>() | |
| 1305 | − }; | |
| 1306 | − let platform = unsafe { get_mac_platform(this) }; | |
| 1307 | − let mut lock = platform.0.lock(); | |
| 1308 | − if let Some(mut callback) = lock.open_urls.take() { | |
| 1309 | − drop(lock); | |
| 1310 | − callback(urls); | |
| 1311 | − platform.0.lock().open_urls.get_or_insert(callback); | |
| 1330 | + } | |
| 1331 | + Err(err) => { | |
| 1332 | + log::error!("error converting URL to string: {}", err); | |
| 1333 | + } | |
| 1334 | + } | |
| 1312 | 1335 | } |
| 1313 | 1336 | } |
| 1314 | 1337 | |