Keyboard input
Keyboard input arrives as key events. A press is Event::KeyPress carrying a
Key; with the kitty keyboard protocol enabled, repeats and releases arrive as
Event::KeyRepeat and Event::KeyRelease. The Key fields you usually use are
code (which key), modifiers (Ctrl, Alt, Shift, and friends), and the optional
text it would produce.
Screen reads are pure. Passing each event to screen.observe_event(&ev)?
after read_event, try_read_event, or an async event stream is optional; it
keeps capability detection, resize tracking, and discovery defaults alive, and
skipping it still reads fine. The ratatui UncursesBackend follows the same contract.Matching keys the easy way
For keybindings, check the shortcut text directly with matches or
matches_any.
use uncurses::event::Event;
let ev = screen.read_event()?;
screen.observe_event(&ev)?;
match ev {
Event::KeyPress(ref k) if k.matches("ctrl+c") => return Ok(()),
Event::KeyPress(ref k) if k.matches("f1") => show_help(),
_ => {}
}The grammar covers plain characters ("q"), named keys ("enter", "esc",
"up", "f1"), and modifier chords ("ctrl+c", "alt+shift+left"). Do not
compare k.to_string() to binding strings; display is structural. Under the
kitty keyboard protocol, a shifted glyph like } may display as shift+], while
k.matches("}") still matches the produced text.
let ev = screen.read_event()?;
screen.observe_event(&ev)?;
match ev {
Event::KeyPress(ref k) if k.matches_any(["q", "esc", "ctrl+c"]) => break,
_ => {}
}Matching keys structurally
When you want to branch on the key itself, match on KeyCode and inspect
modifiers. modifiers is a bitset, so test it with contains and
is_empty.
use uncurses::event::{Event, Key, KeyCode, KeyModifiers};
let ev = screen.read_event()?;
screen.observe_event(&ev)?;
match ev {
Event::KeyPress(Key { code: KeyCode::Char('q'), modifiers, .. })
if modifiers.is_empty() => break,
Event::KeyPress(Key { code: KeyCode::Char('c'), modifiers, .. })
if modifiers.contains(KeyModifiers::CTRL) => break,
Event::KeyPress(Key { code: KeyCode::Left, .. }) => move_left(),
_ => {}
}Space is its own variant, KeyCode::Space, not KeyCode::Char(' '). Match on
KeyCode::Space (it still reports text of " "); a Char(' ') arm never
fires. The other printable-but-named keys follow the same rule: Enter, Tab,
and Backspace are KeyCode::Enter / Tab / Backspace, not Char('\n') /
Char('\t') / Char('\x7f').
Rust has no variant aliases, so you cannot make one arm match both. When you
care about the typed character rather than the physical key, use key.char(),
which returns Some(' ') for both Space and Char(' '), or check
key.text.
The modifier flags are SHIFT, ALT, CTRL, META, HYPER, SUPER,
CAPS_LOCK, and NUM_LOCK.
A Key carries text only for printable input: a character or space with no
modifiers other than Shift, Caps Lock, or Num Lock. Ctrl, Alt, Meta, Hyper, Super,
and named keys (Enter, arrows, function keys) clear it. So a present text
means the key would type something, already in the right layout ("!" for
shift+1), which is what you want for text entry rather than the physical
code.
let ev = screen.read_event()?;
screen.observe_event(&ev)?;
if let Event::KeyPress(k) = ev {
if let Some(text) = &k.text {
buffer.push_str(text); // a printable key: insert what it typed
}
}Presses, repeats, and releases
By default a terminal reports each key once, as a press, and that is all most
apps need. The kitty keyboard protocol can report more, but what you get depends
on which flags you turn on with set_kitty_keyboard. Pass Some(flags) to
enable the bits you want, or None to switch every enhancement back off.
use uncurses::ansi::kitty::KittyKeyboardFlags;
screen.set_kitty_keyboard(Some(
KittyKeyboardFlags::DISAMBIGUATE_ESCAPE_CODES
| KittyKeyboardFlags::REPORT_EVENT_TYPES,
))?;
// ... now your loop can also see, for keys sent as escape codes:
// Event::KeyRepeat(k)
// Event::KeyRelease(k)
The flags combine, and each one enables a specific behavior:
DISAMBIGUATE_ESCAPE_CODESmakes keys that the legacy encoding blurs together distinct:Escon its own,Ctrl+IversusTab, andCtrl+MversusEnterall become unambiguous escape codes.REPORT_EVENT_TYPESaddsKeyRepeatandKeyRelease, but only for keys the terminal sends as escape codes: arrows, function keys, and modified keys that are not sent as text. Keys that produce text are still delivered as plain UTF-8, so a held letter keeps arriving as repeatedKeyPressevents with no release.REPORT_ALL_KEYS_AS_ESCAPEreports every key as an escape code, including printable keys. Combine it withREPORT_EVENT_TYPESwhen you want repeats and releases for letters and digits too, for instance to track a heldwin a game.REPORT_ALTERNATE_KEYSadds shifted and base key values.REPORT_ASSOCIATED_TEXTadds the produced text.
A terminal that does not speak the protocol ignores the request, so you keep getting plain presses and it is safe to ask unconditionally.
See the keylog example in examples/examples/ for a live readout of every key,
modifier, repeat, and release.