Fix "busy" cursor appearing on startup (#44019)

John Tur created

Closes https://github.com/zed-industries/zed/issues/43910

Release Notes:

- N/A

Change summary

Cargo.lock                    |  1 
crates/crashes/Cargo.toml     |  3 +
crates/crashes/src/crashes.rs | 58 +++++++++++++++++++++++++++++++++++++
3 files changed, 62 insertions(+)

Detailed changes

Cargo.lock 🔗

@@ -4184,6 +4184,7 @@ dependencies = [
  "serde_json",
  "smol",
  "system_specs",
+ "windows 0.61.3",
  "zstd 0.11.2+zstd.1.5.2",
 ]
 

crates/crashes/Cargo.toml 🔗

@@ -23,6 +23,9 @@ zstd.workspace = true
 [target.'cfg(target_os = "macos")'.dependencies]
 mach2.workspace = true
 
+[target.'cfg(target_os = "windows")'.dependencies]
+windows.workspace = true
+
 [lints]
 workspace = true
 

crates/crashes/src/crashes.rs 🔗

@@ -3,6 +3,8 @@ use log::info;
 use minidumper::{Client, LoopAction, MinidumpBinary};
 use release_channel::{RELEASE_CHANNEL, ReleaseChannel};
 use serde::{Deserialize, Serialize};
+
+#[cfg(not(target_os = "windows"))]
 use smol::process::Command;
 
 #[cfg(target_os = "macos")]
@@ -70,11 +72,16 @@ pub async fn init(crash_init: InitCrashHandler) {
     // used by the crash handler isn't destroyed correctly which causes it to stay on the file
     // system and block further attempts to initialize crash handlers with that socket path.
     let socket_name = paths::temp_dir().join(format!("zed-crash-handler-{zed_pid}"));
+    #[cfg(not(target_os = "windows"))]
     let _crash_handler = Command::new(exe)
         .arg("--crash-handler")
         .arg(&socket_name)
         .spawn()
         .expect("unable to spawn server process");
+
+    #[cfg(target_os = "windows")]
+    spawn_crash_handler_windows(&exe, &socket_name);
+
     #[cfg(target_os = "linux")]
     let server_pid = _crash_handler.id();
     info!("spawning crash handler process");
@@ -342,6 +349,57 @@ pub fn panic_hook(info: &PanicHookInfo) {
     }
 }
 
+#[cfg(target_os = "windows")]
+fn spawn_crash_handler_windows(exe: &Path, socket_name: &Path) {
+    use std::ffi::OsStr;
+    use std::iter::once;
+    use std::os::windows::ffi::OsStrExt;
+    use windows::Win32::System::Threading::{
+        CreateProcessW, PROCESS_CREATION_FLAGS, PROCESS_INFORMATION, STARTF_FORCEOFFFEEDBACK,
+        STARTUPINFOW,
+    };
+    use windows::core::PWSTR;
+
+    let mut command_line: Vec<u16> = OsStr::new(&format!(
+        "\"{}\" --crash-handler \"{}\"",
+        exe.display(),
+        socket_name.display()
+    ))
+    .encode_wide()
+    .chain(once(0))
+    .collect();
+
+    let mut startup_info = STARTUPINFOW::default();
+    startup_info.cb = std::mem::size_of::<STARTUPINFOW>() as u32;
+
+    // By default, Windows enables a "busy" cursor when a GUI application is launched.
+    // This cursor is disabled once the application starts processing window messages.
+    // Since the crash handler process doesn't process messages, this "busy" cursor stays enabled for a long time.
+    // Disable the cursor feedback to prevent this from happening.
+    startup_info.dwFlags = STARTF_FORCEOFFFEEDBACK;
+
+    let mut process_info = PROCESS_INFORMATION::default();
+
+    unsafe {
+        CreateProcessW(
+            None,
+            Some(PWSTR(command_line.as_mut_ptr())),
+            None,
+            None,
+            false,
+            PROCESS_CREATION_FLAGS(0),
+            None,
+            None,
+            &startup_info,
+            &mut process_info,
+        )
+        .expect("unable to spawn server process");
+
+        windows::Win32::Foundation::CloseHandle(process_info.hProcess).ok();
+        windows::Win32::Foundation::CloseHandle(process_info.hThread).ok();
+    }
+}
+
 pub fn crash_server(socket: &Path) {
     let Ok(mut server) = minidumper::Server::with_name(socket) else {
         log::info!("Couldn't create socket, there may already be a running crash server");