terminal_graphics_interactive
Keyboard and mouse input for terminal_graphics, so you can build interactive terminal games, art, animations, screensavers, maths and physics demos, and drawing tools. One dependency (terminal_graphics, re-exported), almost no unsafe.
Status: early (0.1). Done: the input parser, raw terminal mode, Terminal (an interactive session with poll(timeout), the window size and draw), mouse-to-cell mapping, a paint example, and llms.txt for AI coding agents. Next: a game example and terminal resize events.
Try it
It works in Windows PowerShell and Windows Terminal, Linux and macOS terminals, and over SSH if you allocate a terminal (ssh -t host ...).
How it works
Terminal::start()switches to the alternate screen (your scrollback comes back on exit), puts the terminal in raw mode, turns on mouse reporting, hides the cursor and finds the window size. Dropping it undoes all of that, even on panic.terminal.poll(timeout)returns the next event, orNonewhen the time runs out (your cue to draw the next frame);terminal.wait()blocks. (RawMode,Parser,MOUSE_ONandMOUSE_OFFare public too if you want the pieces.)- Events are
Event::KeyandEvent::Mouse. A lone Escape byte becomes the Escape key after 25 ms, so arrow keys over a slow SSH link are not mistaken for it. terminal.draw(&display)shows aDisplayat the top left of the window, so mouse coordinates (0-based cells,(0, 0)at the top left) line up with display cells:mouse.cell_in(&display)gives the cell under the pointer. Make the display at mostterminal.size()wide and one row shorter than it is high.
unsafe
Raw mode cannot be done in safe Rust, so it is the only place unsafe appears: three libc calls on Unix (src/raw_mode/unix.rs) and three kernel32 calls on Windows (src/raw_mode/windows.rs), each with a // SAFETY: comment. Everything else is safe Rust, and tests/unsafe_audit.rs fails the build if unsafe appears anywhere else.
Performance
cargo bench runs benches/parser.rs, which uses only std. Median of 15 samples on one machine (Windows 11, release build), parsing 10,000 events fed in chunks:
| Stream | Chunk | Before | Now |
|---|---|---|---|
| mouse drag (SGR) | 64 B | 44 ns/event | 41 ns/event |
| mouse drag (SGR) | 4 KB | 134 ns/event | 37 ns/event |
| mouse drag (SGR) | 1 MB paste | 1630 ns/event | 85 ns/event |
| keyboard | 64 B | 102 ns/event | 28 ns/event |
| keyboard | 1 MB paste | 415 ns/event | 27 ns/event |
"Before" had two problems that the benchmark exposed: parsed bytes were removed from the front of the buffer after every event (quadratic on large pastes), and key sequences allocated a Vec. A terminal produces nowhere near 10 million events a second, so parsing is far from being a bottleneck.
Platform checks so far
| Platform | Built and unit tested | Run against a real terminal |
|---|---|---|
| Windows | yes | yes: 8 end-to-end tests in a ConPTY pseudo console (tests/e2e_windows.rs), run in CI and locally. Not yet tried by a person in a real PowerShell or Windows Terminal window |
| Linux, macOS | yes | yes: the same 8 end-to-end tests in a pty (tests/e2e_unix.rs), run in CI on Ubuntu and macOS |
License
Licensed under either of MIT or Apache-2.0 at your option.