Milo's input system reads raw terminal input and translates escape sequences into structuredKeyobjects. It handles arrows, function keys, modifiers, and platform differences.
KeyReader
KeyReader is a context manager that puts the terminal in raw mode and yields Keyobjects:
from milo.input import KeyReader
with KeyReader() as keys:
for key in keys:
print(f"Got: {key.name or key.char}")
if key.ctrl and key.char == "c":
break
Key objects
Each keypress produces a frozenKeydataclass:
Key(
char="a", # The character (or empty for special keys)
name=None, # SpecialKey enum value for non-character keys
ctrl=False, # Ctrl modifier
alt=False, # Alt/Option modifier
shift=False, # Shift modifier
)
Special keys
TheSpecialKeyenum covers all standard terminal keys:
| Category | Keys |
|---|---|
| Arrows | UP, DOWN, LEFT, RIGHT |
| Navigation | HOME, END, PAGE_UP, PAGE_DOWN |
| Editing | INSERT, DELETE, BACKSPACE |
| Control | TAB, ENTER, ESCAPE |
| Function | F1 through F12 |
Escape sequences
Milo includes a frozen lookup table mapping ANSI VT100/xterm escape sequences toKeyobjects. This covers:
- Plain keys (arrows, F-keys, Home, End, etc.)
- Shift+key variants
- Alt+key variants
- Ctrl+key variants
How escape sequence parsing works
When a raw byte stream arrives:
- If the first byte is
\x1b(ESC), read ahead for a complete sequence - Look up the sequence in the frozen table (
_sequences.py) - If found, produce the matching
Keywith the correct modifiers - If not found, produce a
Keywithname=ESCAPE
The frozen lookup table is built at import time — no runtime overhead per keypress.
Platform support
Usestermios + tty for raw mode, selectfor non-blocking reads.
from milo.input._platform import raw_mode, read_char, is_tty
if is_tty():
with raw_mode():
ch = read_char()
Usesmsvcrtfor raw character reads.
# Automatically selected on Windows — same KeyReader API
with KeyReader() as keys:
for key in keys:
...
Tip
Useis_tty()to check if stdin is an interactive terminal before entering raw mode. This lets your app degrade gracefully when input is piped.