fix: defer clipboard init to avoid blocking startup on X11-less hosts#1772
fix: defer clipboard init to avoid blocking startup on X11-less hosts#1772zlh124 wants to merge 1 commit into
Conversation
…osts On Linux, `arboard::Clipboard::new()` opens a blocking connect() to the X11 Unix socket. When no X server is running (headless, WSL2 without WSLg), the call hangs indefinitely. Because raw mode and the alternate screen are already active at that point, Ctrl+C no longer generates SIGINT and the event loop hasn't started yet — leaving the user with a blank screen and no way to exit. Move clipboard initialization from `ClipboardHandler::new()` (called synchronously during App construction) to a lazy `ensure_clipboard()` that runs on first read/write with a 500 ms timeout. If the X11 connection doesn't respond in time, the handler stays in fallback mode and `write_text` falls through to the existing OSC 52 / pbcopy / PowerShell paths.
There was a problem hiding this comment.
Code Review
This pull request introduces lazy initialization for the system clipboard in ClipboardHandler, using a background thread with a 500ms timeout to prevent startup hangs on headless environments or WSL2. Feedback indicates that the current implementation still blocks the main TUI thread during the first clipboard interaction, which could cause UI freezes; a background initialization during startup was suggested as an alternative. Additionally, it was recommended to gate the initialization call in the read method with #[cfg(not(test))] to ensure test suite performance is maintained in CI environments.
| fn ensure_clipboard(&mut self) { | ||
| if self.clipboard_init_attempted { | ||
| return; | ||
| } | ||
| self.clipboard_init_attempted = true; | ||
|
|
||
| let (tx, rx) = std::sync::mpsc::channel(); | ||
| std::thread::spawn(move || { | ||
| let _ = tx.send(Clipboard::new().ok()); | ||
| }); | ||
| // 500 ms is generous for a local Unix socket connect — the | ||
| // kernel either answers or doesn't. | ||
| self.clipboard = rx | ||
| .recv_timeout(std::time::Duration::from_millis(500)) | ||
| .ok() | ||
| .flatten(); | ||
| } |
There was a problem hiding this comment.
This method blocks the main TUI thread for up to 500ms on the first clipboard interaction. In an async application, blocking the event loop for this long causes a noticeable freeze in the user interface.
Consider initiating the connection attempt in a background thread during ClipboardHandler::new and storing the Receiver. This would allow the connection to be established while the user is performing other startup tasks, making the first clipboard use near-instant without blocking the event loop.
| /// `workspace` is used as a fallback location when `~/.deepseek/` cannot | ||
| /// be resolved (e.g. running with a stripped HOME in CI sandboxes). | ||
| pub fn read(&mut self, workspace: &Path) -> Option<ClipboardContent> { | ||
| self.ensure_clipboard(); |
There was a problem hiding this comment.
This call to ensure_clipboard is not gated by #[cfg(not(test))], unlike the corresponding call in write_text. This will introduce a 500ms delay in any test that invokes read in a headless environment (like many CI runners) where the clipboard connection hangs. Gating this call ensures test suite performance is maintained.
| self.ensure_clipboard(); | |
| #[cfg(not(test))] | |
| self.ensure_clipboard(); |
Summary
arboard::Clipboard::new()blocks on X11 connect() during startupand Ctrl+C stops working (raw mode suppresses SIGINT, event loop not started yet)
Test plan
cargo test -p deepseek-tui— all tests passstraceconfirms no X11 connect() during startup (--version,doctor)covers the no-X11 case)
Fixes #1773