uncurses is a Rust library for building terminal user interfaces. It gives you a direct, framework-free way to draw to the terminal and read input — you own every cell and your own event loop, whether you run inline, take over the full screen, or mix the two.
A diffing renderer that redraws only what changed, Unicode-aware width, truecolor styling with automatic downsampling, hyperlinks, and typed keyboard, mouse, and paste input. It asks the terminal what it supports instead of looking it up in a terminfo database, so the same code runs on Linux, macOS, and Windows.
[dependencies]
uncurses = "0.0.3"Using ratatui? uncurses-ratatui provides a backend.
Guides, concepts, and API reference: uncurses.org
Upgrading from 0.0.2
Screen splits into Program and Screen. Program owns the terminal, input, and modes; Screen is now Screen<W: Write> and only draws. Reach the renderer with program.screen() / screen_mut().
let mut program = Program::stdio()?; // was Screen::stdio()
let mut program = Program::open()?; // was Screen::open()ScreenOptions becomes ProgramOptions. In uncurses-ratatui, UncursesBackend::screen / screen_mut become program / program_mut, and try_read_event returns io::Result<Option<Event>>.
Startup no longer queries the terminal. ScreenOptions::query_capabilities is gone; nothing is probed unless you ask for it. Call program.query_capabilities(&[]) when you want discovery. Reading an event no longer writes either, so refresh the inline origin yourself with program.request_origin().
Capabilities records what the terminal replied instead of a fixed set of booleans. capabilities() returns &Capabilities, and the public fields are now accessors:
| 0.0.2 | 0.0.3 |
|---|---|
caps.mouse_sgr_pixel |
caps.supports(Mode::MOUSE_SGR_PIXEL) |
caps.synchronized_output |
caps.supports(Mode::SYNCHRONIZED_OUTPUT) |
caps.grapheme_clusters |
caps.supports(Mode::UNICODE_CORE) |
caps.in_band_resize |
caps.supports(Mode::IN_BAND_RESIZE) |
caps.kitty_keyboard |
caps.kitty_keyboard().is_some() |
caps.true_color |
program.screen().color_profile() == Profile::TrueColor |
caps.sixel, caps.clipboard, da_attribute |
read caps.primary_device_attributes() |
The last row is the one to look at twice. Those accessors picked two numbers out of the Primary DA reply and searched the whole parameter list for them, but the first parameter is the terminal's architectural service class, not a capability: a VT132 answers CSI ? 4 ; 6 c, and the old code read that leading 4 as Sixel support. Read the class first, then decide what the numbers after it mean.
Event::Termcap carries entries: Vec<(String, Option<String>)> instead of a single payload: String, so a multi-entry XTGETTCAP reply no longer needs unpacking by hand. DECRQSS replies are no longer folded in with it and arrive as Event::SettingReport.
Env is a read-only trait. A process environment is live, not fixed. ProcessEnv reads through to the real environment on every lookup, EnvList answers from a fixed ordered list, and Terminal stores a Box<dyn Env> so either works without a type parameter.
BEL terminates OSC only. DCS, APC, PM, and SOS now require ST, per ECMA-48, and a byte in 0x80..=0x9F is treated as C1 only at a character boundary. Input that relied on BEL closing a DCS parses differently.
Changelog
Breaking changes
- Split Screen into a renderer and a Program facade
- Stop querying the terminal behind the caller's back
- Split Screen into a renderer and a Program facade (#25)
- Report DECRPSS separately and keep XTGETTCAP entries structured (#37)
- Store terminal replies in Capabilities, not booleans
- Drop Capabilities::true_color
- Rename da_attribute, drop sixel and clipboard
- Report Primary DA without interpreting it
- Make Env a read-only trait (#33)
- Make tokenizing linear in the length of a line (#14)
Bug Fixes
- Reset the pen before a newline that can scroll (#39)
- Repaint when the width mode changes
- Keep the origin correct across fullscreen and bursts
- Repaint when leaving fullscreen, not only entering
- Observe each event once, and stop the docs asking for twice
- Let poll_event take a shared borrow, and correct three docs
- Hold unread events instead of re-observing them
- Scale mouse pixels across the grid, not by a truncated cell
- Report DECRPSS separately and keep XTGETTCAP entries structured (#37)
- Never adopt a mode over the application's own choice
Documentation
- Correct the claims the purity refactor invalidated
- Correct what capability replies actually apply
- Say that the reported cell size can go stale
- Capabilities fill from any reply, not only query_capabilities
- Stop claiming Terminal.app support is recorded
- Count three
prefer_*fields, not two - Restructure the Program page and drop absence-based prose (#31)
- List the features uncurses supports (#43)
Features
- Adopt grapheme clusters and in-band resize on discovery
- Record every reply the terminal sends about itself
- Expose the terminal and its environment snapshot (#27)
- Complete DECRQSS and DECRPSS support (#29)
- Report terminal visibility (DEC 2033) (#11)
Miscellaneous Tasks
- List breaking changes in the release notes
Performance
- Make tokenizing linear in the length of a line (#14)
Refactor
- Drop tool-branded comment markers (#35)
- Split Screen into a renderer and a Program facade
- Stop querying the terminal behind the caller's back
- Split Screen into a renderer and a Program facade (#25)
- Store terminal replies in Capabilities, not booleans
- Drop Capabilities::true_color
- Rename da_attribute, drop sixel and clipboard
- Report Primary DA without interpreting it
- Make Env a read-only trait (#33)
Styling
- Satisfy the lints stable 1.98 added (#40)
Testing
- Gate the piped read on unix