History

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.

koh-sh committed 7 months ago

Commit 5be9dc11 parent fe26ab61 file · +46 −23Browse files

Unified · Split

crates/gpui_macos/src/platform.rs+46−23
@@ -62,6 +62,9 @@ use util::{ · 61 unmodified lines
62const NSUTF8StringEncoding: NSUInteger = 4;62const NSUTF8StringEncoding: NSUInteger = 4;
63 63
64const MAC_PLATFORM_IVAR: &str = "platform";64const MAC_PLATFORM_IVAR: &str = "platform";
65const INTERNET_EVENT_CLASS: u32 = 0x4755524c; // 'GURL'
66const AE_GET_URL: u32 = 0x4755524c; // 'GURL'
67const DIRECT_OBJECT_KEY: u32 = 0x2d2d2d2d; // '----'
65static mut APP_CLASS: *const Class = ptr::null();68static mut APP_CLASS: *const Class = ptr::null();
66static mut APP_DELEGATE_CLASS: *const Class = ptr::null();69static mut APP_DELEGATE_CLASS: *const Class = ptr::null();
67 70
@@ -136,8 +139,8 @@ unsafe fn build_classes() { · 68 unmodified lines
136 handle_dock_menu as extern "C" fn(&mut Object, Sel, id) -> id,139 handle_dock_menu as extern "C" fn(&mut Object, Sel, id) -> id,
137 );140 );
138 decl.add_method(141 decl.add_method(
139 sel!(application:openURLs:),142 sel!(handleGetURLEvent:withReplyEvent:),
140 open_urls as extern "C" fn(&mut Object, Sel, id, id),143 handle_get_url_event as extern "C" fn(&mut Object, Sel, id, id),
141 );144 );
142 145
143 decl.add_method(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
1181extern "C" fn will_finish_launching(_this: &mut Object, _: Sel, _: id) {1184extern "C" fn will_finish_launching(this: &mut Object, _: Sel, _: id) {
1182 unsafe {1185 unsafe {
1183 let user_defaults: id = msg_send![class!(NSUserDefaults), standardUserDefaults];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 let false_value: id = msg_send![class!(NSNumber), numberWithBool:false];1195 let false_value: id = msg_send![class!(NSNumber), numberWithBool:false];
1193 let _: () = msg_send![user_defaults, setObject: false_value forKey: name];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
1291extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) {1305extern "C" fn handle_get_url_event(this: &mut Object, _: Sel, event: id, _reply: id) {
1292 let urls = unsafe {1306 unsafe {
1293 (0..urls.count())1307 let descriptor: id = msg_send![event, paramDescriptorForKeyword: DIRECT_OBJECT_KEY];
1294 .filter_map(|i| {1308 if descriptor == nil {
1295 let url = urls.objectAtIndex(i);1309 return;
1296 match CStr::from_ptr(url.absoluteString().UTF8String() as *mut c_char).to_str() {1310 }
1297 Ok(string) => Some(string.to_string()),1311 let url_string: id = msg_send![descriptor, stringValue];
1298 Err(err) => {1312 if url_string == nil {
1299 log::error!("error converting path to string: {}", err);1313 return;
1300 None1314 }
1301 }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 })1330 }
1304 .collect::<Vec<_>>()1331 Err(err) => {
1305 };1332 log::error!("error converting URL to string: {}", err);
1306 let platform = unsafe { get_mac_platform(this) };1333 }
1307 let mut lock = platform.0.lock();1334 }
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);
1312 }1335 }
1313}1336}
1314 1337