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:

  1. Explain canonical vs raw mode and why it matters.
  2. Read keypresses byte-by-byte reliably.
  3. Render a deterministic TUI layout with escape sequences.
  4. 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

  1. Save current terminal settings.
  2. Disable canonical mode and echo.
  3. Read input bytes immediately.
  4. Interpret escape sequences.
  5. 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

  1. Why does raw mode require manual echo handling?
  2. What happens if you never restore terminal settings?
  3. How do arrow keys appear in raw input?
  4. Why does SIGWINCH matter for TUIs?

Check-your-understanding answers

  1. The driver no longer echoes characters, so the program must draw them.
  2. The user’s shell will remain in raw mode and appear broken.
  3. As multi-byte escape sequences starting with ESC.
  4. 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

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

  1. Identify the byte sequence for arrow keys in your terminal.
  2. Explain how to restore a terminal using stty.

Solutions to the homework/exercises

  1. Arrow keys begin with ESC [ and a letter.
  2. stty sane restores 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

  1. Raw input: Read keypresses without line buffering.
  2. TUI rendering: Clear and redraw the screen.
  3. 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 ./rawmode in 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

  1. Read bytes.
  2. Decode escape sequences.
  3. Update display state.
  4. 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

  1. Line discipline
    • What does canonical mode do?
    • Book Reference: “The Linux Programming Interface” - terminals chapter
  2. 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

  1. How will you ensure cleanup on crash?
  2. 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

  1. “What is canonical mode?”
  2. “How does Ctrl+C generate SIGINT?”
  3. “What is a pseudo-terminal?”
  4. “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:

  1. Save and modify terminal settings.
  2. Read one byte at a time.

Checkpoint: Keypresses print codes.

Phase 2: Core Functionality (3 days)

Goals:

  • Decode arrow keys and redraw screen.

Tasks:

  1. Parse escape sequences.
  2. Render a simple UI.

Checkpoint: UI updates on each keypress.

Phase 3: Polish & Edge Cases (2 days)

Goals:

  • Handle cleanup and resize.

Tasks:

  1. Restore terminal on exit and SIGINT.
  2. 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

  1. Keypress: letters show correct ASCII code.
  2. Arrow keys: decoded correctly.
  3. 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.
  • 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/

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