Rust bindings for the Curses Development Kit
What is CDK 1?
CDK is a library of functions which allow a programmer to quickly create a full screen interactive program with ease. The CDK widgets support the following features:
Ncurses library. : Instead of using the standard curses library, CDK can take advantage of the colors that Ncurses provides.
Key Bindings. : Individual keys can be overridden with a callback.
Pre and Post Processing. : Certain widgets allow the user to trap a character before and after the character has been applied to the widget. This allows programmers to "filter" character input.
Self Test Widgets. : With the use of the inject function class and the activate function, programmers can have the widgets test themselves.
Special Display Formats. : There are special character format commands that can be inserted into any string in CDK and the contents will get mapped to a chtype (see the curses manual page) with character attributes. This allows the programmer to insert format types on each character if they wish.
The Ability To Build Predefined Screens. : Widgets can be associated to any given screen. If there is more than one screen defined, then CDK has the ability to "flip" from one screen to another with ease.
To develop console-based interfaces, there is a library found on all Linux systems called ncurses. ncurses is to console interfaces what Xlib is to graphical interfaces. It contains everything needed to create console-based interfaces while remaining fairly low-level. Although the standard HOWTO collection includes an Ncurses HOWTO, ncurses is nevertheless not a library that can be approached very intuitively, and a great deal of time must be spent before one can program a satisfactory interface.
There is a higher-level and easier-to-use library that brings together numerous pre-built widgets. It is called CDK, an acronym for Curses Development Kit 1. CDK is a library written in C. Nevertheless, it was designed in a strongly object-oriented style, with each widget having methods in the form of associated functions. A rigorous definition of the structures and the casting of pointers also provides a semblance of inheritance.
- Linux
- CDK5 development files, ncurses, and libclang
Terminal and widget constructors return Result values. Applications should handle
initialization failures and strings containing NUL bytes instead of relying on panics.
This is the Rust version of the classic CDK workflow: create a curses window, attach a CDK screen, add widgets, activate them, and let the owners clean up the terminal when they leave scope.
On Debian or Ubuntu:
sudo apt install cargo clang libcdk5-dev pkg-configcurdk-sys uses the CDK5 development installation. If cdk5-config is not
on PATH, set CDK5_CONFIG to its full path before building.
Window initializes the curses terminal. Screen connects that terminal to
CDK and owns the lifetime required by its widgets:
let window = curdk::Window::new()?;
let screen = curdk::Screen::new(&window)?;
# Ok::<(), curdk::Error>(())Keep the Window and Screen alive while using widgets. Dropping a widget
destroys its native CDK object; dropping the screen restores the terminal.
The constructors accept Rust strings and return an error if CDK rejects the native allocation or a string contains an interior NUL byte:
let window = curdk::Window::new()?;
let screen = curdk::Screen::new(&window)?;
let label = curdk::Label::new(
&screen,
curdk::CENTER,
curdk::TOP,
"Name",
curdk::Border::NONE,
)?;
let entry = curdk::Entry::new(
&screen,
curdk::CENTER,
curdk::CENTER,
"Input",
"Name: ",
curdk::Border::NONE,
)?;
screen.refresh();
let value = entry.activate()?;
label.set_message(&format!("Hello, {value}!"))?;
screen.refresh();
# Ok::<(), curdk::Error>(())Interactive widgets block in activate until the user finishes. Buttons,
button boxes, dialogs, menus, lists, and selections also provide
activate_result(), which returns Activation::Selected(index) or
Activation::Cancelled instead of exposing CDK's raw return code.
Call screen.exit() when the application should leave CDK's main loop. The
normal Rust drop order then releases widgets, the screen, and the terminal.
Widget constructors that wrap CDK's Box / Shadow arguments take a
Border. Use Border::NONE for an unframed widget, Border::BOXED for a
border, or Border::SHADOWED for a border and drop shadow.
Key bindings and pre/post-process hooks are available on every widget:
entry.bind_key(b'q' as u32, |_| 1)?;
entry.set_preprocess(|key| if key == b'!' as u32 { 0 } else { 1 });Return 1 from a key binding to consume the key, 0 to let CDK handle it.
A preprocess callback returns 1 to apply the character and 0 to ignore it.
To share one event loop across a screen of widgets, call screen.traverse().
Tab moves focus; a binding that calls screen.exit() (or screen.cancel())
ends the loop. Screen is cheap to clone so the callback can own a handle:
let done = screen.clone();
entry.bind_key(b'x' as u32 & 0x1f, move |_| {
done.exit();
1
})?;
let accepted = screen.traverse();The repository contains complete examples:
cargo run -p curdk --example counter
cargo run -p curdk --example temperature_converter
cargo run -p curdk --example crud
cargo run -p curdk --example flight_booker
cargo run -p curdk --example alphalist
cargo run -p curdk --example template
cargo run -p curdk --example matrix
cargo run -p curdk --example swindow
cargo run -p curdk --example formThe second part of the original CDK tutorial demonstrates the important step after creating one widget: compose a screen from several focused widgets and let each activation produce an application message. In Rust, keep the application state separate from CDK handles, and make the event loop the only place that changes that state.
enum Message {
Search,
Select(usize),
Quit,
}
fn update(model: &mut Model, message: Message) {
match message {
Message::Search => model.refresh_results(),
Message::Select(index) => model.selected = index,
Message::Quit => model.running = false,
}
}Create widgets once, then update their contents during each view pass. This avoids recreating native objects and keeps ownership predictable:
let results = curdk::Scroll::new(
&screen,
curdk::CENTER,
curdk::CENTER,
curdk::RIGHT,
8,
48,
"Results",
&["First result", "Second result"],
false,
curdk::Border::NONE,
)?;
let actions = curdk::Buttonbox::new(
&screen,
curdk::CENTER,
curdk::BOTTOM,
5,
32,
1,
2,
&["Search", "Quit"],
curdk::Border::NONE,
)?;A typical CDK event loop follows this order:
- Render model state into labels, entries, and lists.
- Refresh the screen once.
- Activate the widget that owns the next interaction.
- Convert its result into a message.
- Apply
updateand repeat until the model requestsQuit.
Use Widget when a function only needs common operations such as drawing or
changing the box. Use widget-specific methods for values, selections, and
activation. Scroll, Radio, and Selection accept Rust slices, convert
their strings for the duration of the native call, and expose current-item
accessors. Selection additionally exposes per-item boolean choices.
For a complete application-shaped version of this pattern, see the
counter, booker, crud, calculator, and temperature_converter
examples. form shows Screen::traverse over several widgets. This section
is adapted from the archived
second CDK tutorial.
- AlphaList
- Button
- ButtonBox
- Calendar
- Dialog
- Entry — entry field
- FSelect — file selector
- FViewer — file viewer
- Graph
- Histogram
- Scale — integer scale
- Slider — integer slider
- ItemList
- Label
- Marquee
- Matrix
- Mentry — multiple line entry field
- Menu — pulldown menu
- Radio — radio list
- Scroll — scrolling list
- Selection — scrolling selection list
- SWindow — scrolling window
- Template — template entry field