Skip to content

Commit 2b5c986

Browse files
committed
fix: stop teardown drains at the first non-mouse event
All three mouse-report drains (driver shutdown, init-exit shutdown, and the RawModeGuard drop fallback) read and discarded every event type for up to 50ms, so keystrokes typed immediately after an app exited could be eaten. The spray is contiguous: stop at the first non-mouse event, capping collateral at the single event already consumed.
1 parent 51ab60a commit 2b5c986

2 files changed

Lines changed: 23 additions & 5 deletions

File tree

crates/eye_declare/src/driver_tokio.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,11 @@ where
198198
/// stream's reader thread owns crossterm's shared reader, so synchronous
199199
/// `event::poll` sees nothing while the reports flow into the stream.
200200
/// Bounded: quiet for 5ms or 50ms total.
201+
///
202+
/// The spray is contiguous, so the first non-mouse event ends the drain:
203+
/// keystrokes typed during the window belong to the shell, and eating
204+
/// them would lose input. The one event already consumed is the price of
205+
/// not being able to push it back.
201206
async fn shutdown_mouse(
202207
guard: &mut RawModeGuard,
203208
stdout: &mut impl Write,
@@ -215,15 +220,16 @@ async fn shutdown_mouse(
215220
)
216221
.await
217222
{
218-
Ok(Some(_)) => {}
219-
// Quiet, or the stream ended: done.
223+
Ok(Some(Ok(crossterm::event::Event::Mouse(_)))) => {}
224+
// Quiet, a non-mouse event, a read error, or stream end: done.
220225
_ => break,
221226
}
222227
}
223228
}
224229

225230
/// The pre-loop exit (`App::init` exited): no stream exists yet, so the
226231
/// synchronous drain works — no reader thread is holding the source.
232+
/// Stops at the first non-mouse event, like [`shutdown_mouse`].
227233
fn shutdown_mouse_sync(guard: &mut RawModeGuard, stdout: &mut impl Write) {
228234
if !guard.take_mouse_capture() {
229235
return;
@@ -233,7 +239,12 @@ fn shutdown_mouse_sync(guard: &mut RawModeGuard, stdout: &mut impl Write) {
233239
while std::time::Instant::now() < deadline
234240
&& matches!(crossterm::event::poll(Duration::from_millis(5)), Ok(true))
235241
{
236-
let _ = crossterm::event::read();
242+
if !matches!(
243+
crossterm::event::read(),
244+
Ok(crossterm::event::Event::Mouse(_))
245+
) {
246+
break;
247+
}
237248
}
238249
}
239250

crates/eye_declare/src/runtime.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -676,15 +676,22 @@ impl Drop for RawModeGuard {
676676
// in the parent shell's input as escape-sequence garbage — and
677677
// has been seen to wedge fragile emulators outright. Drain
678678
// until quiet, bounded, while raw mode is still on. Costs up to
679-
// 50ms at teardown, only for capture-enabled apps.
679+
// 50ms at teardown, only for capture-enabled apps. The spray is
680+
// contiguous, so the first non-mouse event ends the drain:
681+
// keystrokes typed during the window belong to the shell.
680682
let deadline = std::time::Instant::now() + std::time::Duration::from_millis(50);
681683
while std::time::Instant::now() < deadline
682684
&& matches!(
683685
crossterm::event::poll(std::time::Duration::from_millis(5)),
684686
Ok(true)
685687
)
686688
{
687-
let _ = crossterm::event::read();
689+
if !matches!(
690+
crossterm::event::read(),
691+
Ok(crossterm::event::Event::Mouse(_))
692+
) {
693+
break;
694+
}
688695
}
689696
}
690697
if self.alt_screen {

0 commit comments

Comments
 (0)