Project 6: Raw Mode Terminal (TUI Core)
Build a raw-mode terminal program that reads keypresses directly and renders a simple text UI.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | Advanced |
| Time Estimate | 1 week |
| Main Programming Language | C (Alternatives: Rust, Go) |
| Alternative Programming Languages | Rust, Go |
| Coolness Level | See REFERENCE.md (Level 3) |
| Business Potential | See REFERENCE.md (Level 2) |
| Prerequisites | File descriptors, signals, terminal basics |
| Key Topics | termios, TTY line discipline, escape sequences |
1. Learning Objectives
By completing this project, you will:
- Explain canonical vs raw mode and why it matters.
- Read keypresses byte-by-byte reliably.
- Render a deterministic TUI layout with escape sequences.
- Restore terminal state even after errors.
2. All Theory Needed (Per-Concept Breakdown)
TTY Line Discipline and Raw Mode
Fundamentals A terminal is a device that converts user input into bytes and converts output into screen updates. By default, terminals use canonical mode: input is buffered until newline, characters are echoed, and special key combinations generate signals. Raw mode disables much of this behavior so programs can process each keypress directly. This is essential for interactive terminal applications like text editors and full-screen TUIs. Understanding line discipline and raw mode is necessary to avoid stuck terminals and to handle escape sequences for special keys.
Deep Dive The terminal driver sits between your program and the physical terminal or terminal emulator. It maintains a line discipline that controls how input is processed before your program receives it. In canonical mode, the driver buffers input until a line delimiter is seen, handles backspace locally, and echoes typed characters back to the screen. It also interprets special control characters: Ctrl+C sends SIGINT, Ctrl+Z sends SIGTSTP, and Ctrl+\ sends SIGQUIT. This behavior is convenient for line-oriented programs but prevents low-level control.
Raw mode disables these transformations. Characters are delivered immediately, echo is turned off, and control characters are passed as bytes rather than signals. This is how programs detect arrow keys, function keys, and other special inputs. These keys emit multi-byte escape sequences, typically starting with the ESC character. The program must parse those sequences to understand user intent. For example, arrow keys are often represented as ESC [ A/B/C/D. In raw mode, the program receives these bytes directly and must interpret them itself.
Output is also controlled by escape sequences. Terminal emulators understand ANSI escape codes that move the cursor, clear the screen, and change text attributes. A TUI program redraws the screen by clearing it, moving the cursor, and printing the current state. This is not a graphics API; it is a stream of bytes interpreted by the terminal emulator. The program must keep track of cursor position and avoid leaving the terminal in a broken state.
Signals still matter in raw mode. If you disable signal generation, Ctrl+C will no longer stop the program. This is often desired, but it requires you to implement your own quit or interrupt handling. If you leave signal generation enabled, you must handle SIGINT to restore terminal settings before exiting. The most common failure mode is leaving the terminal in raw mode after a crash, which makes the shell appear broken. A robust program registers cleanup handlers and performs state restoration on all exit paths.
Finally, terminals can change size at runtime. When the window is resized, the kernel delivers SIGWINCH to the foreground process group. A TUI program should handle this signal by recomputing layout and redrawing. This is essential for a good user experience and helps reinforce that signals are part of the interactive control plane, not just error handling.
How this fit on projects You will apply this concept in §3.1 to define the TUI behavior, in §4.1 for screen rendering design, and in §5.8 for debugging strategies. It also supports P03-build-your-own-shell.md where terminal signals matter.
Definitions & key terms
- TTY: Terminal device interface with line discipline.
- Canonical mode: Line-buffered input with local editing.
- Raw mode: Immediate input, no echo, minimal processing.
- Escape sequence: Byte sequence that controls terminal behavior.
- SIGWINCH: Signal sent on terminal resize.
Mental model diagram
Keyboard -> TTY driver -> (line discipline) -> program
|
v
signals
How it works
- Save current terminal settings.
- Disable canonical mode and echo.
- Read input bytes immediately.
- Interpret escape sequences.
- Restore terminal settings on exit.
Minimal concrete example
Input bytes (conceptual):
'a' -> 0x61
ArrowUp -> 0x1b 0x5b 0x41
Common misconceptions
- “Raw mode only disables echo.” It changes buffering and signal handling.
- “Ctrl+C is a character.” It is usually a signal.
- “Terminal output is graphics.” It is a byte stream with escape codes.
Check-your-understanding questions
- Why does raw mode require manual echo handling?
- What happens if you never restore terminal settings?
- How do arrow keys appear in raw input?
- Why does SIGWINCH matter for TUIs?
Check-your-understanding answers
- The driver no longer echoes characters, so the program must draw them.
- The user’s shell will remain in raw mode and appear broken.
- As multi-byte escape sequences starting with ESC.
- The layout changes with window size and must be redrawn.
Real-world applications
- Text editors and CLI dashboards.
- Terminal-based monitoring tools.
- Interactive shells with line editing.
Where you’ll apply it
- See §3.1 What You Will Build and §4.1 High-Level Design.
- Also used in: P03-build-your-own-shell.md
References
- termios(3) man page: https://man7.org/linux/man-pages/man3/termios.3.html
- “The Linux Programming Interface” - terminals chapter
Key insights Raw mode hands control of input and display to your program.
Summary If you control line discipline and escape sequences, you control the terminal.
Homework/Exercises to practice the concept
- Identify the byte sequence for arrow keys in your terminal.
- Explain how to restore a terminal using
stty.
Solutions to the homework/exercises
- Arrow keys begin with ESC [ and a letter.
stty sanerestores canonical mode and echo.
3. Project Specification
3.1 What You Will Build
A raw-mode program named rawmode that displays a simple full-screen TUI and prints keypress diagnostics. It supports quit on q, shows arrow key decoding, and restores terminal state on exit.
3.2 Functional Requirements
- Raw input: Read keypresses without line buffering.
- TUI rendering: Clear and redraw the screen.
- Safe exit: Restore terminal settings on exit and on SIGINT.
3.3 Non-Functional Requirements
- Performance: Screen redraws should be flicker-free.
- Reliability: Terminal must never be left in raw mode.
- Usability: Clear on-screen instructions.
3.4 Example Usage / Output
[Screen clears]
RAW MODE ACTIVE (press q to quit)
Last key: a (97)
3.5 Data Formats / Schemas / Protocols
- Escape sequence decoding for arrow keys.
- ASCII key code display.
3.6 Edge Cases
- Program crashes mid-session.
- Unsupported terminal emulator.
- Window resize while running.
3.7 Real World Outcome
3.7.1 How to Run (Copy/Paste)
- Build in
project-root. - Run
./rawmodein a terminal.
3.7.2 Golden Path Demo (Deterministic)
Press a fixed sequence: a, ArrowUp, q.
3.7.3 If CLI: Exact terminal transcript
$ ./rawmode
[screen clears]
RAW MODE ACTIVE (press q to quit)
Last key: a (97)
Last key: ArrowUp (ESC [ A)
# exit code: 0
Failure demo (deterministic):
$ ./rawmode
error: not a TTY
# exit code: 2
Exit codes:
- 0 success
- 2 not running on a TTY
3.7.8 If TUI:
ASCII TUI layout:
+--------------------------------------+
| RAW MODE ACTIVE (press q to quit) |
| |
| Last key: a (97) |
| Last key: ArrowUp (ESC [ A) |
| |
+--------------------------------------+
4. Solution Architecture
4.1 High-Level Design
Init terminal -> event loop -> decode -> render -> cleanup
4.2 Key Components
| Component | Responsibility | Key Decisions |
|---|---|---|
| Terminal config | Switch to raw mode | Save and restore settings |
| Input decoder | Parse bytes into keys | Handle escape sequences |
| Renderer | Draw screen each loop | Full redraw for simplicity |
4.4 Data Structures (No Full Code)
- Terminal state: saved settings + current settings.
- Key event: type, code, printable label.
4.4 Algorithm Overview
Key Algorithm: input loop
- Read bytes.
- Decode escape sequences.
- Update display state.
- Redraw screen.
Complexity Analysis:
- Time: O(n) per frame for n characters drawn.
- Space: O(1) for small state.
5. Implementation Guide
5.1 Development Environment Setup
# Ensure termios is available and terminal is a TTY
5.2 Project Structure
project-root/
├── src/
│ ├── rawmode.c
│ └── render.c
├── tests/
│ └── tty_tests.sh
└── README.md
5.3 The Core Question You’re Answering
“What is the terminal doing on my behalf, and how do I take control?”
5.4 Concepts You Must Understand First
- Line discipline
- What does canonical mode do?
- Book Reference: “The Linux Programming Interface” - terminals chapter
- Signals from terminal
- How does Ctrl+C become SIGINT?
- Book Reference: “Advanced Programming in the UNIX Environment” - signals
5.5 Questions to Guide Your Design
- How will you ensure cleanup on crash?
- How will you decode multi-byte sequences?
5.6 Thinking Exercise
Stuck Terminal Recovery
Write the command sequence you would use to restore a broken terminal.
5.7 The Interview Questions They’ll Ask
- “What is canonical mode?”
- “How does Ctrl+C generate SIGINT?”
- “What is a pseudo-terminal?”
- “Why do TUIs need to handle SIGWINCH?”
5.8 Hints in Layers
Hint 1: Save settings Store the original termios settings before changing them.
Hint 2: Disable canonical mode and echo Read input immediately without line buffering.
Hint 3: Parse escape sequences Arrow keys start with ESC [.
Hint 4: Debugging
If the terminal breaks, run stty sane.
5.9 Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| Terminals | “The Linux Programming Interface” | Ch. 62 |
| Signals | “Advanced Programming in the UNIX Environment” | Ch. 10 |
5.10 Implementation Phases
Phase 1: Foundation (2 days)
Goals:
- Enter raw mode and read a byte.
Tasks:
- Save and modify terminal settings.
- Read one byte at a time.
Checkpoint: Keypresses print codes.
Phase 2: Core Functionality (3 days)
Goals:
- Decode arrow keys and redraw screen.
Tasks:
- Parse escape sequences.
- Render a simple UI.
Checkpoint: UI updates on each keypress.
Phase 3: Polish & Edge Cases (2 days)
Goals:
- Handle cleanup and resize.
Tasks:
- Restore terminal on exit and SIGINT.
- Handle SIGWINCH for resize.
Checkpoint: Terminal always returns to normal.
5.11 Key Implementation Decisions
| Decision | Options | Recommendation | Rationale |
|---|---|---|---|
| Rendering | Full redraw vs diff | Full redraw | Simpler for learning |
| Signal handling | Ignore vs handle | Handle | Prevent broken terminal |
6. Testing Strategy
6.1 Test Categories
| Category | Purpose | Examples |
|---|---|---|
| Unit Tests | Decoder correctness | ESC sequence parsing |
| Integration Tests | Full run | manual keypresses |
| Edge Case Tests | Resize events | SIGWINCH handling |
6.2 Critical Test Cases
- Keypress: letters show correct ASCII code.
- Arrow keys: decoded correctly.
- Exit: terminal restored.
6.3 Test Data
Key sequence: a, ArrowUp, q
Expected: printed codes and clean exit
7. Common Pitfalls & Debugging
7.1 Frequent Mistakes
| Pitfall | Symptom | Solution |
|---|---|---|
| No cleanup | Terminal broken | Restore settings on exit |
| Bad parsing | Arrow keys show as junk | Parse ESC sequences |
| No SIGWINCH handling | UI corrupt on resize | Redraw on resize |
7.2 Debugging Strategies
- Use stty: inspect terminal flags.
- Log raw bytes: print hex values to see sequences.
7.3 Performance Traps
Constant full redraws can flicker; throttle redraw when idle.
8. Extensions & Challenges
8.1 Beginner Extensions
- Add a status bar with help text.
- Highlight control keys.
8.2 Intermediate Extensions
- Add line editing.
- Add search mode.
8.3 Advanced Extensions
- Build a minimal text editor.
- Support multiple panels.
9. Real-World Connections
9.1 Industry Applications
- Editors: vim, nano, micro.
- Monitoring tools: htop, btop.
9.2 Related Open Source Projects
- kilo editor: https://viewsourcecode.org/snaptoken/kilo/ - minimal editor tutorial.
- ncurses: https://invisible-island.net/ncurses/ - TUI library.
9.3 Interview Relevance
Terminal handling and signals are common systems interview topics.
10. Resources
10.1 Essential Reading
- termios(3) man page
- “The Linux Programming Interface” - terminals chapter
10.2 Video Resources
- “Terminal internals” - lectures (search title)
10.3 Tools & Documentation
- man7: https://man7.org/linux/man-pages/man3/termios.3.html
- ncurses: https://invisible-island.net/ncurses/
10.4 Related Projects in This Series
11. Self-Assessment Checklist
11.1 Understanding
- I can explain canonical vs raw mode
- I can decode escape sequences
- I understand why cleanup is critical
11.2 Implementation
- All functional requirements are met
- Terminal always restores
- UI updates correctly
11.3 Growth
- I can explain this project in an interview
- I documented lessons learned
- I can propose an extension
12. Submission / Completion Criteria
Minimum Viable Completion:
- Raw mode input works
- UI renders and updates
- Terminal restores on exit
Full Completion:
- All minimum criteria plus:
- Arrow keys decoded
- SIGWINCH handled
Excellence (Going Above & Beyond):
- Build a minimal editor
- Add configuration and themes