diff --git a/crates/pikl/src/main.rs b/crates/pikl/src/main.rs index 019b8bb..1f1a0a0 100644 --- a/crates/pikl/src/main.rs +++ b/crates/pikl/src/main.rs @@ -662,20 +662,33 @@ async fn run_interactive( result } FrontendMode::Gui => { - // GUI runs on the main thread (Wayland requirement). // Spawn the core event loop on a background task. let menu_handle = tokio::spawn(menu.run()); - // pikl_gui::run blocks until the user confirms/cancels. - // Run it on a blocking thread so we don't stall the - // tokio runtime. - let gui_handle = tokio::task::spawn_blocking(move || { + // macOS requires the GUI event loop on the main thread + // (AppKit constraint). block_on runs on the main thread, + // so calling the GUI directly here satisfies that. + // Spawned tasks (menu, signals, streaming) continue on + // worker threads. + // + // On Linux, spawn_blocking is fine since Wayland doesn't + // have the main-thread restriction. + #[cfg(target_os = "macos")] + { pikl_gui::run(action_tx, event_rx) - .map_err(|e| PiklError::Io(std::io::Error::other(e.to_string()))) - }); + .map_err(|e| PiklError::Io(std::io::Error::other(e.to_string())))?; + } - if let Err(e) = gui_handle.await { - return Err(PiklError::Io(std::io::Error::other(e.to_string()))); + #[cfg(not(target_os = "macos"))] + { + let gui_handle = tokio::task::spawn_blocking(move || { + pikl_gui::run(action_tx, event_rx) + .map_err(|e| PiklError::Io(std::io::Error::other(e.to_string()))) + }); + + if let Err(e) = gui_handle.await { + return Err(PiklError::Io(std::io::Error::other(e.to_string()))); + } } // Wait for core to finish.