fix
This commit is contained in:
@@ -177,7 +177,12 @@ fn setup_bridge(
|
|||||||
action_tx: mpsc::Sender<ModifiedAction>,
|
action_tx: mpsc::Sender<ModifiedAction>,
|
||||||
event_rx: broadcast::Receiver<MenuEvent>,
|
event_rx: broadcast::Receiver<MenuEvent>,
|
||||||
) {
|
) {
|
||||||
let _ = action_tx.blocking_send(ModifiedAction::new(Action::Resize {
|
// try_send rather than blocking_send: this is the first
|
||||||
|
// message on a fresh channel so it always has capacity,
|
||||||
|
// and try_send works regardless of whether we're inside
|
||||||
|
// a tokio runtime context (macOS calls this from the
|
||||||
|
// main thread outside block_on).
|
||||||
|
let _ = action_tx.try_send(ModifiedAction::new(Action::Resize {
|
||||||
height: VIEWPORT_HEIGHT,
|
height: VIEWPORT_HEIGHT,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -365,14 +365,23 @@ fn main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// STEP 4: Branch on headless vs interactive
|
// STEP 4: Branch on headless vs interactive
|
||||||
|
#[cfg(unix)]
|
||||||
|
let saved_fd = saved_stdin_fd;
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
let saved_fd: Option<i32> = None;
|
||||||
|
|
||||||
let result = if let Some(script) = script {
|
let result = if let Some(script) = script {
|
||||||
rt.block_on(run_headless(items, &cli, script, start_mode, column_config))
|
rt.block_on(run_headless(items, &cli, script, start_mode, column_config))
|
||||||
|
} else if cfg!(target_os = "macos") && frontend_mode == FrontendMode::Gui {
|
||||||
|
// macOS requires the GUI event loop on the main thread
|
||||||
|
// (AppKit/winit constraint). We can't run it inside
|
||||||
|
// block_on, so the GUI gets the main thread and tokio
|
||||||
|
// work is spawned onto the runtime's worker threads.
|
||||||
|
run_gui_main_thread(
|
||||||
|
&rt, items, &cli, start_mode, column_config, streaming, saved_fd,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
let history_entries = history.as_ref().map(|h| h.entries().to_vec());
|
let history_entries = history.as_ref().map(|h| h.entries().to_vec());
|
||||||
#[cfg(unix)]
|
|
||||||
let saved_fd = saved_stdin_fd;
|
|
||||||
#[cfg(not(unix))]
|
|
||||||
let saved_fd: Option<i32> = None;
|
|
||||||
rt.block_on(run_interactive(
|
rt.block_on(run_interactive(
|
||||||
items,
|
items,
|
||||||
&cli,
|
&cli,
|
||||||
@@ -662,13 +671,11 @@ async fn run_interactive(
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
FrontendMode::Gui => {
|
FrontendMode::Gui => {
|
||||||
// GUI runs on the main thread (Wayland requirement).
|
|
||||||
// Spawn the core event loop on a background task.
|
// Spawn the core event loop on a background task.
|
||||||
let menu_handle = tokio::spawn(menu.run());
|
let menu_handle = tokio::spawn(menu.run());
|
||||||
|
|
||||||
// pikl_gui::run blocks until the user confirms/cancels.
|
// On Linux, spawn_blocking is fine since Wayland doesn't
|
||||||
// Run it on a blocking thread so we don't stall the
|
// have the main-thread restriction.
|
||||||
// tokio runtime.
|
|
||||||
let gui_handle = tokio::task::spawn_blocking(move || {
|
let gui_handle = tokio::task::spawn_blocking(move || {
|
||||||
pikl_gui::run(action_tx, event_rx)
|
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())))
|
||||||
@@ -687,6 +694,133 @@ async fn run_interactive(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Run GUI on the main thread for macOS. AppKit requires
|
||||||
|
/// the event loop on the main thread, so we can't use
|
||||||
|
/// block_on (which would nest the iced event loop inside
|
||||||
|
/// tokio's reactor). Instead, spawn all background work
|
||||||
|
/// onto the runtime's worker threads and run the GUI
|
||||||
|
/// directly.
|
||||||
|
fn run_gui_main_thread(
|
||||||
|
rt: &tokio::runtime::Runtime,
|
||||||
|
items: Vec<Item>,
|
||||||
|
cli: &Cli,
|
||||||
|
start_mode: Mode,
|
||||||
|
column_config: Option<ColumnConfig>,
|
||||||
|
streaming: bool,
|
||||||
|
saved_stdin_fd: Option<i32>,
|
||||||
|
) -> Result<MenuResult, PiklError> {
|
||||||
|
let (mut menu, action_tx) = MenuRunner::new(build_menu(items, cli, column_config));
|
||||||
|
menu.set_initial_mode(start_mode);
|
||||||
|
menu.set_multi(cli.multi);
|
||||||
|
menu.set_selection_order(cli.selection_order);
|
||||||
|
menu.set_streaming(streaming);
|
||||||
|
|
||||||
|
if let Some((_handler, dispatcher)) = build_hook_handler(cli, &action_tx) {
|
||||||
|
menu.set_dispatcher(dispatcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
let event_rx = menu.subscribe();
|
||||||
|
|
||||||
|
// Start IPC server if requested
|
||||||
|
let _ipc_server = if cli.ipc {
|
||||||
|
let sock_path = ipc::socket_path(cli.session.as_deref())?;
|
||||||
|
let server = ipc::server::IpcServer::new(
|
||||||
|
sock_path,
|
||||||
|
action_tx.clone(),
|
||||||
|
menu.event_sender(),
|
||||||
|
);
|
||||||
|
let ipc_event_rx = menu.subscribe();
|
||||||
|
server.start(ipc_event_rx).map_err(PiklError::Io)?;
|
||||||
|
Some(server)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// Enter the runtime context so tokio::spawn works
|
||||||
|
// without being inside block_on.
|
||||||
|
let _guard = rt.enter();
|
||||||
|
|
||||||
|
// Signal handler
|
||||||
|
let signal_tx = action_tx.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
match tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) {
|
||||||
|
Ok(mut sigterm) => {
|
||||||
|
tokio::select! {
|
||||||
|
_ = tokio::signal::ctrl_c() => {}
|
||||||
|
_ = sigterm.recv() => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!(%e, "failed to register SIGTERM handler, falling back to SIGINT only");
|
||||||
|
let _ = tokio::signal::ctrl_c().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let _ = signal_tx.send(ModifiedAction::new(Action::Cancel)).await;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Streaming stdin reader
|
||||||
|
if let Some(fd) = saved_stdin_fd {
|
||||||
|
let stream_tx = action_tx.clone();
|
||||||
|
tokio::task::spawn_blocking(move || {
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
use std::io::BufRead;
|
||||||
|
use std::os::unix::io::FromRawFd;
|
||||||
|
|
||||||
|
let file = unsafe { std::fs::File::from_raw_fd(fd) };
|
||||||
|
let mut reader = std::io::BufReader::new(file);
|
||||||
|
let mut batch = Vec::with_capacity(100);
|
||||||
|
let mut line = String::new();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
line.clear();
|
||||||
|
match reader.read_line(&mut line) {
|
||||||
|
Ok(0) => break,
|
||||||
|
Ok(_) => {
|
||||||
|
let trimmed = line.trim_end();
|
||||||
|
if !trimmed.is_empty() {
|
||||||
|
batch.push(parse_line_to_value(trimmed));
|
||||||
|
}
|
||||||
|
if (batch.len() >= 100 || reader.buffer().is_empty())
|
||||||
|
&& !batch.is_empty()
|
||||||
|
{
|
||||||
|
let _ = stream_tx.blocking_send(ModifiedAction::new(
|
||||||
|
Action::AddItems(std::mem::take(&mut batch)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!(%e, "stdin read error");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !batch.is_empty() {
|
||||||
|
let _ =
|
||||||
|
stream_tx.blocking_send(ModifiedAction::new(Action::AddItems(batch)));
|
||||||
|
}
|
||||||
|
let _ =
|
||||||
|
stream_tx.blocking_send(ModifiedAction::new(Action::StreamingDone));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Core menu on a background task
|
||||||
|
let menu_handle = tokio::spawn(menu.run());
|
||||||
|
|
||||||
|
// GUI on the main thread (the whole point of this function)
|
||||||
|
pikl_gui::run(action_tx, event_rx)
|
||||||
|
.map_err(|e| PiklError::Io(std::io::Error::other(e.to_string())))?;
|
||||||
|
|
||||||
|
// GUI exited. Collect the menu result.
|
||||||
|
rt.block_on(async {
|
||||||
|
match menu_handle.await {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(e) => Err(PiklError::Io(std::io::Error::other(e.to_string()))),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Process the menu result: print output to stdout and
|
/// Process the menu result: print output to stdout and
|
||||||
/// exit with the appropriate code.
|
/// exit with the appropriate code.
|
||||||
fn handle_result(result: Result<MenuResult, PiklError>, cli: &Cli) {
|
fn handle_result(result: Result<MenuResult, PiklError>, cli: &Cli) {
|
||||||
|
|||||||
Reference in New Issue
Block a user