Open unixzii opened 8 months ago
I noticed that the parser parsed Esc
key press because the tty buffer is insufficient.
The byte sequence read from stdin
in one poll ends with 27
, which is cut off. Maybe there are some bugs in the terminal emulator that the control sequence is not written atomically. Or the operating system doesn't give us the whole buffer at once.
I have a similar issue with iTerm2.
After further investigation, it seems to be an issue related to Blocking I/O. The problem is resolved by switching between Blocking and NonBlocking before and after executing crossterm::event::read() as shown below.
use crossterm::{
event::{DisableMouseCapture, EnableMouseCapture},
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
execute,
};
use ratatui::{
backend::CrosstermBackend,
Terminal,
};
use std::time::Duration;
#[cfg(any(target_os = "linux", target_os = "macos"))]
use std::io::{self, stdin};
use std::os::unix::io::AsRawFd;
#[cfg(any(target_os = "linux", target_os = "macos"))]
use nix::fcntl::{fcntl, FcntlArg::*, OFlag};
#[cfg(any(target_os = "linux", target_os = "macos"))]
fn set_non_blocking() -> nix::Result<()> {
let stdin_fd = stdin().as_raw_fd();
let flags = fcntl(stdin_fd, F_GETFL)?;
let new_flags = OFlag::from_bits_truncate(flags) | OFlag::O_NONBLOCK;
fcntl(stdin_fd, F_SETFL(new_flags))?;
Ok(())
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
pub fn set_blocking() -> nix::Result<()> {
let stdin_fd = stdin().as_raw_fd();
let flags = fcntl(stdin_fd, F_GETFL)?;
let new_flags = OFlag::from_bits_truncate(flags) & !OFlag::O_NONBLOCK;
fcntl(stdin_fd, F_SETFL(new_flags))?;
Ok(())
}
fn main() -> Result<(), io::Error> {
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
let _ = terminal.clear();
loop {
if crossterm::event::poll(Duration::from_millis(100))? {
#[cfg(any(target_os = "linux", target_os = "macos"))]
let _ = set_non_blocking();
// read event
if let Ok(event) = crossterm::event::read() {
match event {
crossterm::event::Event::Key(key) => {
if key.code == crossterm::event::KeyCode::Esc {
break;
}
}
_ => {}
}
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
let _ = set_blocking();
}
}
disable_raw_mode()?;
let backend = CrosstermBackend::new(io::stdout());
let mut terminal = Terminal::new(backend)?;
let _ = execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
);
let _ = terminal.show_cursor();
print!("catch esc keyinput event.");
Ok(())
}
Considering usability, it seems preferable to switch between Blocking/NonBlocking within the read() function itself.
Describe the bug
event::read
sometimes returnsEsc
key press event while scrolling the mouse wheel very fast. This will likely happen on macOS if the user is using a trackpad (which can produce high frequency events because of the inertia simulation).Precondition: The terminal is in a mode that translates mouse wheel events into arrow key events.
To Reproduce The minimal reproducible example:
Steps to reproduce the behavior:
Expected behavior The program should not receive
Esc
key press events when the user didn't press Esc key.OS Verified on macOS 13.6
Terminal/Console