← Back to all projects

TERMINAL EMULATOR DEEP DIVE PROJECTS

Terminal Emulator Deep Dive Projects

Goal: Deeply understand how terminal emulators work, from PTY internals to GPU-accelerated rendering, and build your own terminal from scratch.


Core Concepts: Terminals Demystified

The Great Confusion: Terminal vs Shell vs Console

Most people confuse these terms. Let’s clarify once and for all:

┌─────────────────────────────────────────────────────────────────────────┐
│                     YOUR SCREEN (What you see)                          │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  ┌────────────────────────────────────────────────────────────────────┐ │
│  │                    TERMINAL EMULATOR                                │ │
│  │         (iTerm2, Ghostty, Alacritty, GNOME Terminal)               │ │
│  │                                                                     │ │
│  │  Responsibilities:                                                  │ │
│  │  • Render text and colors on screen                                │ │
│  │  • Capture keyboard input                                          │ │
│  │  • Interpret escape sequences (colors, cursor movement)            │ │
│  │  • Handle fonts, scrollback, selection, copy/paste                 │ │
│  └──────────────────────────┬─────────────────────────────────────────┘ │
│                             │                                            │
│                      PTY Master                                          │
│                    (User-space side)                                     │
│                             │                                            │
├─────────────────────────────┼────────────────────────────────────────────┤
│           KERNEL            │                                            │
│                             │                                            │
│                    ┌────────┴────────┐                                   │
│                    │  TTY DRIVER     │                                   │
│                    │  (Line Discipline)│                                 │
│                    │                 │                                   │
│                    │ • Echo characters│                                  │
│                    │ • Line editing  │                                   │
│                    │ • Signal generation│                                │
│                    │   (Ctrl+C → SIGINT)│                                │
│                    └────────┬────────┘                                   │
│                             │                                            │
│                      PTY Slave                                           │
│                   (/dev/pts/N)                                           │
│                             │                                            │
├─────────────────────────────┼────────────────────────────────────────────┤
│                             │                                            │
│  ┌──────────────────────────┴─────────────────────────────────────────┐ │
│  │                         SHELL                                       │ │
│  │              (bash, zsh, fish, nushell)                            │ │
│  │                                                                     │ │
│  │  Responsibilities:                                                  │ │
│  │  • Parse and execute commands                                      │ │
│  │  • Manage environment variables                                    │ │
│  │  • Handle job control (fg, bg, jobs)                              │ │
│  │  • Scripting language interpreter                                  │ │
│  └──────────────────────────┬─────────────────────────────────────────┘ │
│                             │                                            │
│                     Child Processes                                      │
│                   (vim, git, python, etc.)                              │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

Quick Definitions

Term What It Is Examples
Terminal (Physical) Historical hardware device with screen + keyboard VT100, VT220, ADM-3A
Terminal Emulator Software that emulates a physical terminal iTerm2, Ghostty, Alacritty, xterm
Shell Command interpreter program bash, zsh, fish, PowerShell
Console The system’s primary/physical terminal Linux VT (Ctrl+Alt+F1), macOS Console.app
TTY Teletype - the kernel’s terminal subsystem /dev/tty, /dev/pts/*
PTY Pseudo-Terminal - virtual terminal pair /dev/ptmx (master), /dev/pts/N (slave)

Historical Context: Why Terminals Exist

1960s-1970s: Physical Terminals (Teletypes)
┌──────────────┐        Serial Cable        ┌──────────────┐
│   VT100      │ ========================== │   Mainframe  │
│  (Terminal)  │        (RS-232)            │   Computer   │
│              │                            │              │
│ • Keyboard   │ ───── Characters ────────> │ • CPU        │
│ • CRT Screen │ <──── Characters ───────── │ • Storage    │
└──────────────┘                            └──────────────┘

Today: Terminal Emulators
┌───────────────────────────────────────────────────────────┐
│                     Single Computer                        │
│                                                            │
│  ┌─────────────┐         PTY         ┌─────────────────┐  │
│  │  Terminal   │ ◄═══════════════════►│     Shell       │  │
│  │  Emulator   │    (Virtual Serial)  │   + Programs    │  │
│  │  (iTerm2)   │                      │   (bash, vim)   │  │
│  └─────────────┘                      └─────────────────┘  │
│                                                            │
└───────────────────────────────────────────────────────────┘

The PTY (Pseudo-Terminal) is the key abstraction: it makes the shell and programs think they’re connected to a real physical terminal, even though it’s all software.


The PTY: Heart of Terminal Emulation

How PTY Works

// Simplified view of what happens when you open a terminal

// 1. Terminal emulator opens the PTY master
int master_fd = posix_openpt(O_RDWR);
grantpt(master_fd);     // Set permissions
unlockpt(master_fd);    // Unlock slave

// 2. Get path to slave (/dev/pts/N)
char* slave_path = ptsname(master_fd);

// 3. Fork a child process for the shell
if (fork() == 0) {
    // Child: becomes the shell

    // Create new session (become session leader)
    setsid();

    // Open slave PTY - becomes controlling terminal
    int slave_fd = open(slave_path, O_RDWR);

    // Redirect stdin/stdout/stderr to slave
    dup2(slave_fd, STDIN_FILENO);
    dup2(slave_fd, STDOUT_FILENO);
    dup2(slave_fd, STDERR_FILENO);

    // Execute shell
    execl("/bin/bash", "bash", NULL);
}

// Parent: terminal emulator
// - read(master_fd) gets output from shell
// - write(master_fd) sends input to shell

Data Flow

┌─────────────────────────────────────────────────────────────────────┐
│                           TERMINAL EMULATOR                          │
│                                                                      │
│  ┌──────────────┐        ┌──────────────┐        ┌──────────────┐  │
│  │   Keyboard   │ ────>  │   Input      │ ────>  │   PTY        │  │
│  │   Handler    │        │   Processing │        │   Master     │  │
│  └──────────────┘        └──────────────┘        └──────┬───────┘  │
│                                                         │           │
│                                                         │ write()   │
│                                                         ▼           │
├─────────────────────────────────────────────────────────────────────┤
│                              KERNEL                                  │
│                                                                      │
│                         ┌──────────────┐                            │
│                         │    TTY       │                            │
│                         │   Driver     │                            │
│                         │              │                            │
│                         │ • Echo       │                            │
│                         │ • Line edit  │                            │
│                         │ • Signals    │                            │
│                         └──────┬───────┘                            │
│                                │                                     │
├────────────────────────────────┼────────────────────────────────────┤
│                                ▼                                     │
│  ┌──────────────┐        ┌──────────────┐        ┌──────────────┐  │
│  │   Display    │ <────  │   Escape     │ <────  │   PTY        │  │
│  │   Renderer   │        │   Parser     │        │   Slave      │  │
│  └──────────────┘        └──────────────┘        └──────────────┘  │
│                                                         ▲           │
│                           SHELL (bash)                  │           │
│                              │                          │           │
│                              └──────────────────────────┘           │
│                                    stdout/stderr                     │
└─────────────────────────────────────────────────────────────────────┘

Escape Sequences: The Terminal’s Language

When a program wants to do more than print text (change colors, move cursor, clear screen), it sends escape sequences:

Common Escape Sequences (ANSI/VT100)

ESC = \x1b = \033 = ^[

CURSOR MOVEMENT:
  \x1b[H        Move cursor to home (0,0)
  \x1b[5;10H    Move cursor to row 5, column 10
  \x1b[A        Move cursor up 1 line
  \x1b[B        Move cursor down 1 line
  \x1b[C        Move cursor right 1 column
  \x1b[D        Move cursor left 1 column
  \x1b[5A       Move cursor up 5 lines

SCREEN CONTROL:
  \x1b[2J       Clear entire screen
  \x1b[K        Clear from cursor to end of line
  \x1b[1K       Clear from start of line to cursor
  \x1b[2K       Clear entire line

COLORS (SGR - Select Graphic Rendition):
  \x1b[0m       Reset all attributes
  \x1b[1m       Bold
  \x1b[4m       Underline
  \x1b[31m      Red foreground
  \x1b[42m      Green background
  \x1b[38;5;196m   256-color foreground (color 196)
  \x1b[38;2;255;100;0m   24-bit RGB foreground

TERMINAL QUERIES:
  \x1b[6n       Query cursor position (terminal responds: \x1b[row;colR)
  \x1b[c        Query device attributes
  \x1b[?1049h   Switch to alternate screen buffer
  \x1b[?1049l   Switch back to main screen buffer

Example: What ls --color Actually Sends

$ ls --color

Terminal receives:
  \x1b[0m\x1b[01;34mDocuments\x1b[0m  \x1b[01;34mDownloads\x1b[0m  file.txt

Decoded:
  \x1b[0m       → Reset colors
  \x1b[01;34m   → Bold + Blue foreground
  Documents    → Text "Documents"
  \x1b[0m       → Reset colors
  (space)
  \x1b[01;34m   → Bold + Blue foreground
  Downloads    → Text "Downloads"
  \x1b[0m       → Reset colors
  (space)
  file.txt     → Text "file.txt" (default color)

What Differentiates Terminal Emulators?

Comparison of Modern Terminals

Feature iTerm2 Ghostty Alacritty Kitty WezTerm Warp
Rendering CPU GPU (Metal) GPU (OpenGL) GPU (OpenGL) GPU (OpenGL) GPU
Language Obj-C Zig + Swift Rust Python + C Rust Rust
Tabs/Splits Yes Yes No* Yes Yes Yes
Ligatures Yes Yes No Yes Yes Yes
Images Yes Yes No Yes (Protocol) Yes Yes
GPU Text No Yes Yes Yes Yes Yes
Config GUI TOML TOML Custom Lua GUI
Latency ~35ms ~25ms ~25ms ~29ms ~32ms ~30ms

*Alacritty intentionally omits tabs/splits, expecting users to use tmux/Zellij.

Why GPU Rendering Matters

CPU Rendering (Traditional):
┌─────────────────────────────────────────────────────────────┐
│ 1. Rasterize each glyph to bitmap (CPU)                    │
│ 2. Copy bitmap to window buffer (CPU)                      │
│ 3. Composite with background (CPU)                         │
│ 4. Send to display                                          │
│                                                              │
│ Problem: At 4K resolution, this becomes a bottleneck       │
│ Solution: Cache glyphs, but still CPU-bound                │
└─────────────────────────────────────────────────────────────┘

GPU Rendering (Modern):
┌─────────────────────────────────────────────────────────────┐
│ 1. Upload glyph textures to GPU (once)                     │
│ 2. Build vertex buffer of character positions (CPU)        │
│ 3. GPU renders all glyphs in parallel                      │
│ 4. Display                                                  │
│                                                              │
│ Benefit: Scales to 8K, handles thousands of characters     │
│ Terminals: Alacritty, Kitty, Ghostty, WezTerm             │
└─────────────────────────────────────────────────────────────┘

Architecture Comparison

Ghostty’s Layered Architecture:

┌─────────────────────────────────────────────────────────────┐
│                     Platform UI                              │
│            (SwiftUI on macOS, GTK on Linux)                 │
├─────────────────────────────────────────────────────────────┤
│                      apprt                                   │
│              (Platform abstraction layer)                    │
├─────────────────────────────────────────────────────────────┤
│                     libghostty                               │
│  ┌─────────────┬─────────────┬─────────────┬─────────────┐ │
│  │  Terminal   │    Font     │   Renderer  │     PTY     │ │
│  │  Emulation  │   Shaping   │   (Metal/   │   Manager   │ │
│  │  (VT100+)   │  (HarfBuzz) │   OpenGL)   │             │ │
│  └─────────────┴─────────────┴─────────────┴─────────────┘ │
└─────────────────────────────────────────────────────────────┘

Alacritty’s Minimal Architecture:

┌─────────────────────────────────────────────────────────────┐
│                    Alacritty                                 │
│  ┌─────────────────────────────────────────────────────────┐│
│  │              Single Rust Binary                         ││
│  │                                                          ││
│  │  ┌─────────────┬─────────────┬─────────────┐           ││
│  │  │   winit     │   vte       │   glyph     │           ││
│  │  │  (Window)   │  (Parser)   │  (Render)   │           ││
│  │  └─────────────┴─────────────┴─────────────┘           ││
│  │                                                          ││
│  │  No tabs, no splits, no scrollback mouse, no ligatures  ││
│  │  → Use tmux for those features                          ││
│  └─────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘

Can You Interact With Shell Without a Terminal?

Yes! Here are the alternatives:

1. Direct PTY Manipulation (Programmatic)

import pty
import os
import subprocess

# Create PTY pair
master_fd, slave_fd = pty.openpty()

# Spawn shell connected to slave
proc = subprocess.Popen(
    ['/bin/bash'],
    stdin=slave_fd,
    stdout=slave_fd,
    stderr=slave_fd,
    close_fds=True
)

# Now you can read/write through master_fd
os.write(master_fd, b"echo hello\n")
output = os.read(master_fd, 1024)
print(output)  # b'echo hello\r\nhello\r\n'

2. Expect/Pexpect (Automation)

import pexpect

# Spawn interactive program
child = pexpect.spawn('ssh user@server')

# Wait for password prompt and respond
child.expect('password:')
child.sendline('my_password')

# Wait for shell prompt
child.expect(r'\$')

# Run commands
child.sendline('ls -la')
child.expect(r'\$')
print(child.before)  # Output of ls command

3. SSH Without Terminal (-T flag)

# With terminal allocation (default):
ssh user@server    # Allocates PTY, interactive shell

# Without terminal allocation:
ssh -T user@server 'ls -la'   # No PTY, just runs command
ssh -T user@server << 'EOF'   # No PTY, runs script
  echo "Hello"
  whoami
EOF

4. Plain Pipes (For Non-Interactive Programs)

import subprocess

# This works for non-interactive programs
result = subprocess.run(
    ['grep', 'pattern', 'file.txt'],
    capture_output=True,
    text=True
)
print(result.stdout)

# But THIS won't work for interactive programs like vim, ssh:
# subprocess.run(['vim', 'file.txt'])  # vim needs a TTY!

5. Web-Based Terminals (xterm.js, ttyd)

// xterm.js - Terminal in the browser
import { Terminal } from 'xterm';
import { AttachAddon } from 'xterm-addon-attach';

const term = new Terminal();
term.open(document.getElementById('terminal'));

// Connect to backend via WebSocket
const socket = new WebSocket('ws://localhost:3000/terminal');
const attachAddon = new AttachAddon(socket);
term.loadAddon(attachAddon);

// Backend (Node.js) creates PTY and bridges to WebSocket

Why PTY is Still Usually Required

Many programs detect if they’re running in a terminal:

// Programs check if stdout is a TTY
if (isatty(STDOUT_FILENO)) {
    // Running in terminal: use colors, interactive UI
    enable_colors();
    show_progress_bar();
} else {
    // Running in pipe/redirect: plain output
    disable_colors();
    batch_mode();
}

This is why pexpect uses PTYs internally—many programs won’t behave interactively without one.


Project Progression Path

Level 1: Fundamentals
    │
    ├── Project 1: PTY Explorer Tool
    ├── Project 2: Escape Sequence Parser
    ├── Project 3: termios Mode Experimenter
    │
Level 2: Building Blocks
    │
    ├── Project 4: Minimal Terminal (100 lines)
    ├── Project 5: ANSI Color Renderer
    ├── Project 6: Scrollback Buffer Implementation
    │
Level 3: Real Features
    │
    ├── Project 7: VT100 State Machine
    ├── Project 8: Terminal Multiplexer (Mini-tmux)
    ├── Project 9: Font Rendering with FreeType
    │
Level 4: Advanced
    │
    ├── Project 10: GPU-Accelerated Renderer
    ├── Project 11: Sixel/Image Protocol Support
    ├── Project 12: OSC Sequences (Clipboard, Hyperlinks)
    │
Level 5: Integration
    │
    ├── Project 13: Full Terminal Emulator
    ├── Project 14: Web Terminal (xterm.js backend)
    │
Level 6: Capstone
    │
    └── Project 15: Feature-Complete Terminal

Project 1: PTY Explorer Tool

  • File: TERMINAL_EMULATOR_DEEP_DIVE_PROJECTS.md
  • Main Programming Language: C
  • Alternative Programming Languages: Rust, Go, Python
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 1. The “Resume Gold” (Educational/Personal Brand)
  • Difficulty: Level 2: Intermediate (The Developer)
  • Knowledge Area: PTY / Unix System Programming
  • Software or Tool: PTY Debug Tool
  • Main Book: “The Linux Programming Interface” by Michael Kerrisk

What you’ll build: A command-line tool that creates PTY pairs, shows their file descriptors, demonstrates master/slave communication, and visualizes what the TTY driver does.

Why it teaches terminal internals: The PTY is the foundation of all terminal emulation. Understanding it is like understanding the socket layer for networking—everything builds on top of it.

Core challenges you’ll face:

  • PTY creation → posix_openpt, grantpt, unlockpt, ptsname
  • Session management → setsid, controlling terminal concepts
  • File descriptor juggling → dup2, understanding stdin/stdout/stderr
  • Process forking → Parent/child communication patterns
  • Observing TTY driver → Seeing echo, line editing in action

Key Concepts:

Difficulty: Intermediate Time estimate: 1 week Prerequisites: Basic C, understanding of fork/exec

Real world outcome:

$ ./pty_explorer

=== PTY Explorer v1.0 ===

[1] Creating PTY pair...
    Master FD: 3
    Slave path: /dev/pts/5

[2] Current terminal: /dev/pts/2
    Session ID: 12345
    Process Group: 12345
    Foreground PG: 12345

[3] Forking shell on PTY slave...
    Child PID: 12350
    Child session: 12350 (new session!)
    Child controlling TTY: /dev/pts/5

[4] Demonstrating TTY driver (canonical mode):
    Type something and press Enter...
    > hello world<BACKSPACE><BACKSPACE>rld

    Characters received by shell: "hello world" (backspaces processed!)
    Raw bytes through master: "hello wo\x7f\x7frld\r" (backspaces visible!)

[5] Switching to raw mode...
    Type something (each keypress sent immediately)...
    'h' -> immediately sent
    'e' -> immediately sent
    'l' -> immediately sent
    Ctrl+C -> SIGINT sent to child!

[6] PTY window size: 80x24
    Setting to 120x40...
    SIGWINCH sent to child!

Implementation Hints:

#include <pty.h>
#include <utmp.h>
#include <termios.h>

int main() {
    int master_fd;
    char slave_name[256];

    // Create PTY pair (Linux-specific helper)
    pid_t pid = forkpty(&master_fd, slave_name, NULL, NULL);

    if (pid == 0) {
        // Child: we're now connected to the PTY slave
        printf("I'm in the slave PTY: %s\n", ttyname(STDIN_FILENO));
        execlp("bash", "bash", NULL);
    } else {
        // Parent: we have the master
        printf("Master FD: %d, Slave: %s\n", master_fd, slave_name);

        // Now we can read/write to master_fd to communicate with bash
        // This is what a terminal emulator does!
    }
}

The key insight: forkpty() is a convenience function that does everything—but understanding each step (posix_openpt → grantpt → unlockpt → fork → setsid → open slave → dup2) is crucial.

Learning milestones:

  1. PTY pair created successfully → You understand the master/slave concept
  2. Shell spawned on slave → You understand session leaders and controlling terminals
  3. Can observe echo/line-editing → You understand the TTY driver’s role
  4. Raw mode works → You understand termios modes
  5. SIGWINCH works → You understand terminal resize signaling

Project 2: Escape Sequence Parser

  • File: TERMINAL_EMULATOR_DEEP_DIVE_PROJECTS.md
  • Main Programming Language: C
  • Alternative Programming Languages: Rust, Zig, Go
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 1. The “Resume Gold” (Educational/Personal Brand)
  • Difficulty: Level 2: Intermediate (The Developer)
  • Knowledge Area: Parsing / State Machines / ANSI
  • Software or Tool: ANSI Parser
  • Main Book: “Language Implementation Patterns” by Terence Parr

What you’ll build: A parser that takes raw terminal output (bytes from a program) and decodes it into structured events: “print ‘Hello’”, “set color to red”, “move cursor to (5,10)”, etc.

Why it teaches terminal internals: The escape sequence parser is the brain of a terminal emulator. Every byte from the PTY must be classified: is it printable text, or part of a control sequence? This is a beautiful state machine problem.

Core challenges you’ll face:

  • State machine design → Parsing multi-byte sequences correctly
  • Partial sequence handling → What if ESC arrives but [ hasn’t yet?
  • Parameter parsing → “\x1b[5;10;42m” has three numeric parameters
  • Distinguishing sequences → CSI vs OSC vs DCS vs SS3
  • Handling malformed input → Graceful degradation

Key Concepts:

Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Understanding of state machines, basic C

Real world outcome:

$ echo -e "\x1b[31mHello\x1b[0m World" | ./ansi_parser

Parsing input: <ESC>[31mHello<ESC>[0m World

Events:
  [1] CSI Sequence: SGR (Select Graphic Rendition)
      Parameters: [31]
      Action: Set foreground color to RED

  [2] Print: "Hello"

  [3] CSI Sequence: SGR (Select Graphic Rendition)
      Parameters: [0]
      Action: Reset all attributes

  [4] Print: " World"

  [5] Print: "\n"

$ cat /some/program/output | ./ansi_parser --stats
Parsed 45,231 bytes:
  - Printable characters: 42,100
  - CSI sequences: 847
  - OSC sequences: 12
  - Unknown/ignored: 23

Implementation Hints: The parser is a state machine:

typedef enum {
    STATE_GROUND,      // Normal text
    STATE_ESCAPE,      // Just saw ESC
    STATE_CSI_ENTRY,   // Saw ESC [
    STATE_CSI_PARAM,   // Collecting parameters
    STATE_CSI_INTERMEDIATE,
    STATE_OSC_STRING,  // OSC sequence
    // ... more states
} ParserState;

typedef struct {
    ParserState state;
    int params[16];     // CSI parameters
    int param_count;
    char intermediates[4];
} Parser;

void parser_feed(Parser* p, uint8_t byte) {
    switch (p->state) {
    case STATE_GROUND:
        if (byte == 0x1b) {
            p->state = STATE_ESCAPE;
        } else if (byte >= 0x20 && byte < 0x7f) {
            emit_print(byte);
        } else if (byte < 0x20) {
            handle_c0_control(byte);  // TAB, CR, LF, etc.
        }
        break;

    case STATE_ESCAPE:
        if (byte == '[') {
            p->state = STATE_CSI_ENTRY;
            reset_params(p);
        } else if (byte == ']') {
            p->state = STATE_OSC_STRING;
        }
        // ... handle other cases
        break;

    case STATE_CSI_PARAM:
        if (byte >= '0' && byte <= '9') {
            // Accumulate digit
            p->params[p->param_count] *= 10;
            p->params[p->param_count] += byte - '0';
        } else if (byte == ';') {
            p->param_count++;
        } else if (byte >= 0x40 && byte <= 0x7e) {
            // Final byte - dispatch CSI
            dispatch_csi(byte, p->params, p->param_count + 1);
            p->state = STATE_GROUND;
        }
        break;
    }
}

Reference: Paul Williams’ state machine diagram (used by libvterm, Ghostty, etc.)

Learning milestones:

  1. Basic CSI sequences parsed → Colors, cursor movement work
  2. Parameters correctly extracted → Multi-parameter sequences work
  3. Partial sequences buffered → Handles streaming input
  4. OSC sequences work → Title setting, hyperlinks
  5. Full VT100 compatibility → vim, htop, etc. parse correctly

Project 3: termios Mode Experimenter

  • File: TERMINAL_EMULATOR_DEEP_DIVE_PROJECTS.md
  • Main Programming Language: C
  • Alternative Programming Languages: Rust, Python
  • Coolness Level: Level 2: Practical but Forgettable
  • Business Potential: 1. The “Resume Gold” (Educational/Personal Brand)
  • Difficulty: Level 1: Beginner (The Tinkerer)
  • Knowledge Area: termios / TTY Configuration
  • Software or Tool: termios Explorer
  • Main Book: “The Linux Programming Interface” by Michael Kerrisk

What you’ll build: An interactive tool that lets you toggle termios flags in real-time and see how terminal behavior changes (echo, canonical mode, signal characters, etc.).

Why it teaches terminal internals: termios is how programs configure the TTY driver. Understanding these flags explains why vim feels different from cat, and why Ctrl+C sometimes doesn’t work.

Core challenges you’ll face:

  • termios structure → Understanding c_iflag, c_oflag, c_cflag, c_lflag
  • Raw mode setup → The exact flags needed for character-at-a-time input
  • Signal characters → How Ctrl+C becomes SIGINT
  • Restoring state → Properly cleaning up on exit
  • Special characters → VMIN, VTIME for non-blocking reads

Key Concepts:

Difficulty: Beginner Time estimate: 3-5 days Prerequisites: Basic C

Real world outcome:

$ ./termios_explorer

=== termios Explorer ===

Current mode: CANONICAL (cooked)
  ECHO: ON    ICANON: ON    ISIG: ON    ICRNL: ON

Commands:
  [e] Toggle ECHO      [c] Toggle ICANON (canonical)
  [s] Toggle ISIG      [r] Enter raw mode
  [q] Quit (restore terminal)

Current: ECHO=ON, type something:
> hello    (you see what you type)

[e] Toggling ECHO OFF...

Current: ECHO=OFF, type something:
>          (you type but see nothing... eerie!)

[c] Toggling ICANON OFF (non-canonical mode)...

Now every keypress is immediate:
  Pressed: 'h' (0x68)
  Pressed: 'e' (0x65)
  Pressed: 'l' (0x6c)
  Pressed: Ctrl+C (0x03) -> SIGINT would be sent if ISIG=ON

[r] Entering full raw mode...
  All processing disabled.
  Pressed: UP ARROW = 0x1b 0x5b 0x41 (escape sequence!)
  Pressed: Ctrl+C = 0x03 (no signal, just a byte!)

[q] Restoring terminal and exiting...
$

Implementation Hints:

#include <termios.h>
#include <unistd.h>

struct termios original;

void enter_raw_mode() {
    tcgetattr(STDIN_FILENO, &original);

    struct termios raw = original;

    // Input flags: disable almost everything
    raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);

    // Output flags: disable post-processing
    raw.c_oflag &= ~(OPOST);

    // Control flags: set 8-bit chars
    raw.c_cflag |= (CS8);

    // Local flags: disable echo, canonical, signals, extended
    raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);

    // Special characters
    raw.c_cc[VMIN] = 0;   // Return immediately with whatever is available
    raw.c_cc[VTIME] = 1;  // Wait up to 100ms

    tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
}

void restore_terminal() {
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &original);
}

// Always restore on exit!
void setup_exit_handler() {
    atexit(restore_terminal);
    signal(SIGINT, exit);  // Ctrl+C should still work
}

Learning milestones:

  1. ECHO toggle works → You see how echo is handled by TTY driver
  2. ICANON toggle works → You understand line vs character mode
  3. ISIG toggle works → You understand how Ctrl+C becomes a signal
  4. Full raw mode works → You can read escape sequences as bytes
  5. Terminal always restored → Proper cleanup on any exit

Project 4: Minimal Terminal Emulator (100 Lines)

  • File: TERMINAL_EMULATOR_DEEP_DIVE_PROJECTS.md
  • Main Programming Language: C
  • Alternative Programming Languages: Go, Rust
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 1. The “Resume Gold” (Educational/Personal Brand)
  • Difficulty: Level 2: Intermediate (The Developer)
  • Knowledge Area: Terminal Emulation / System Programming
  • Software or Tool: Minimal Terminal
  • Main Book: “The Linux Programming Interface” by Michael Kerrisk

What you’ll build: The smallest possible functional terminal emulator—just PTY handling and stdin/stdout forwarding, no escape sequence processing.

Why it teaches terminal internals: This is the “Hello World” of terminal emulators. Strip away all complexity to see the essence: create PTY, fork shell, shuttle bytes back and forth.

Core challenges you’ll face:

  • PTY setup → The complete sequence to create a working terminal
  • I/O multiplexing → select/poll/epoll to handle bidirectional data
  • Non-blocking I/O → Don’t block on either end
  • Signal handling → SIGCHLD when shell exits, SIGWINCH for resize
  • Proper cleanup → Close FDs, wait for child

Key Concepts:

Difficulty: Intermediate Time estimate: 3-5 days Prerequisites: Projects 1-3

Real world outcome:

$ ./miniterm

# This is a real shell running in your terminal emulator!
$ ls
Documents  Downloads  file.txt
$ vim test.txt    # vim works!
$ htop            # htop works! (kind of - no escape processing yet)
$ exit

[miniterm] Shell exited with status 0
$

At this stage, programs that use escape sequences will look garbled (you see the raw escapes), but the terminal is fully functional for basic use.

Implementation Hints:

#include <pty.h>
#include <sys/select.h>
#include <unistd.h>
#include <signal.h>

volatile sig_atomic_t child_exited = 0;

void sigchld_handler(int sig) { child_exited = 1; }

int main() {
    int master;
    pid_t pid = forkpty(&master, NULL, NULL, NULL);

    if (pid == 0) {
        // Child: exec shell
        execlp(getenv("SHELL") ?: "/bin/sh", "sh", NULL);
        _exit(1);
    }

    // Parent: terminal emulator
    signal(SIGCHLD, sigchld_handler);

    // Set stdin to raw mode (see Project 3)
    enter_raw_mode();

    while (!child_exited) {
        fd_set fds;
        FD_ZERO(&fds);
        FD_SET(STDIN_FILENO, &fds);
        FD_SET(master, &fds);

        if (select(master + 1, &fds, NULL, NULL, NULL) < 0) {
            if (errno == EINTR) continue;
            break;
        }

        char buf[4096];

        // Input from user -> PTY master
        if (FD_ISSET(STDIN_FILENO, &fds)) {
            ssize_t n = read(STDIN_FILENO, buf, sizeof(buf));
            if (n > 0) write(master, buf, n);
        }

        // Output from PTY master -> display
        if (FD_ISSET(master, &fds)) {
            ssize_t n = read(master, buf, sizeof(buf));
            if (n > 0) write(STDOUT_FILENO, buf, n);
        }
    }

    restore_terminal();
    wait(NULL);
    return 0;
}

That’s the core loop! About 50 lines. The rest is error handling and edge cases.

Learning milestones:

  1. Shell spawns and runs → Basic PTY works
  2. Keystrokes reach shell → Input path works
  3. Output appears on screen → Output path works
  4. Exit handled gracefully → SIGCHLD works
  5. Ctrl+C works → Signal forwarding works

Project 5: ANSI Color Renderer

  • File: TERMINAL_EMULATOR_DEEP_DIVE_PROJECTS.md
  • Main Programming Language: C
  • Alternative Programming Languages: Rust, Go
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 1. The “Resume Gold” (Educational/Personal Brand)
  • Difficulty: Level 2: Intermediate (The Developer)
  • Knowledge Area: ANSI / Color Handling / Rendering
  • Software or Tool: Color Renderer
  • Main Book: “Computer Graphics from Scratch” by Gabriel Gambetta

What you’ll build: Extend Project 4’s terminal to interpret SGR (Select Graphic Rendition) sequences and render colored output, using a simple terminal graphics library (ncurses or direct ANSI to host terminal).

Why it teaches terminal internals: Color is the most visible escape sequence feature. Implementing it teaches you the full pipeline: parse sequence → update state → render with state.

Core challenges you’ll face:

  • SGR parsing → Multiple parameters in one sequence
  • Color model understanding → 16-color, 256-color, 24-bit RGB
  • State management → Track current foreground, background, attributes
  • Default handling → What “reset” means, default colors
  • Attribute stacking → Bold + underline + color combined

Key Concepts:

Difficulty: Intermediate Time estimate: 1 week Prerequisites: Project 2, Project 4

Real world outcome:

$ ls --color | ./color_terminal
# Now you see colors!

$ ./color_terminal
$ htop
# htop displays with full color support!

$ echo -e "\x1b[1;31;44mBold Red on Blue\x1b[0m"
# Renders correctly!

Implementation Hints:

typedef struct {
    uint8_t fg_color;      // 0-255 for 256-color mode
    uint8_t bg_color;
    uint32_t fg_rgb;       // For true color
    uint32_t bg_rgb;
    bool bold;
    bool italic;
    bool underline;
    bool blink;
    bool inverse;
    bool fg_is_rgb;
    bool bg_is_rgb;
} CellAttributes;

CellAttributes current_attrs;

void handle_sgr(int* params, int count) {
    for (int i = 0; i < count; i++) {
        int p = params[i];

        switch (p) {
        case 0:  // Reset
            current_attrs = default_attrs();
            break;
        case 1:  current_attrs.bold = true; break;
        case 4:  current_attrs.underline = true; break;
        case 7:  current_attrs.inverse = true; break;

        // Foreground colors 30-37
        case 30: case 31: case 32: case 33:
        case 34: case 35: case 36: case 37:
            current_attrs.fg_color = p - 30;
            current_attrs.fg_is_rgb = false;
            break;

        // 256-color: ESC[38;5;Nm
        case 38:
            if (i + 2 < count && params[i+1] == 5) {
                current_attrs.fg_color = params[i+2];
                current_attrs.fg_is_rgb = false;
                i += 2;
            } else if (i + 4 < count && params[i+1] == 2) {
                // True color: ESC[38;2;R;G;Bm
                current_attrs.fg_rgb = (params[i+2] << 16) |
                                       (params[i+3] << 8) |
                                        params[i+4];
                current_attrs.fg_is_rgb = true;
                i += 4;
            }
            break;
        }
    }
}

Learning milestones:

  1. Basic 8 colors work → SGR 30-37, 40-47
  2. Bright colors work → SGR 90-97, 100-107
  3. 256 colors work → SGR 38;5;N
  4. True color works → SGR 38;2;R;G;B
  5. Attributes combine → Bold + color + underline all work together

Project 6: Scrollback Buffer Implementation

  • File: TERMINAL_EMULATOR_DEEP_DIVE_PROJECTS.md
  • Main Programming Language: C
  • Alternative Programming Languages: Rust, Zig
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 3: Advanced (The Engineer)
  • Knowledge Area: Data Structures / Memory Management
  • Software or Tool: Scrollback Buffer
  • Main Book: “Data Structures the Fun Way” by Jeremy Kubica

What you’ll build: A memory-efficient scrollback buffer that stores terminal history, supporting scrolling, search, and selection.

Why it teaches terminal internals: Every terminal needs scrollback, but naively storing every line wastes memory. Real terminals use clever data structures: ring buffers, copy-on-write, compressed storage.

Core challenges you’ll face:

  • Ring buffer design → Efficient wraparound without copying
  • Line storage → Variable-length lines with attributes
  • Memory limits → Handling millions of lines
  • Viewport management → Tracking what’s visible
  • Selection handling → Start/end coordinates across scrollback

Key Concepts:

  • Ring Buffers: “Data Structures the Fun Way” Chapter 8 - Jeremy Kubica
  • Memory Management: “Computer Systems: A Programmer’s Perspective” Chapter 9 - Bryant & O’Hallaron
  • Terminal Scrollback: Alacritty’s scrollback implementation (Rust source)

Difficulty: Advanced Time estimate: 2 weeks Prerequisites: Strong understanding of pointers and memory

Real world outcome:

$ ./terminal_with_scrollback

# Run something that produces lots of output
$ find / 2>/dev/null
...thousands of lines...

# Now you can scroll back!
# Shift+PageUp: scroll up
# Shift+PageDown: scroll down
# Ctrl+Shift+F: search in scrollback

Search: "Documents"
Found 47 matches. Press n/N to navigate.

# Selection works across scrollback too

Implementation Hints:

// Each line is stored with its attributes
typedef struct {
    uint32_t* cells;      // Character + attributes packed
    uint16_t length;      // Actual used length
    uint16_t capacity;    // Allocated capacity
    bool wrapped;         // Line was wrapped (not explicit newline)
} Line;

typedef struct {
    Line* lines;           // Array of lines
    size_t capacity;       // Max lines
    size_t head;           // Oldest line
    size_t tail;           // Newest line
    size_t count;          // Current count

    // Viewport
    size_t scroll_offset;  // Lines scrolled back
    size_t viewport_rows;  // Visible rows
} ScrollbackBuffer;

void scrollback_add_line(ScrollbackBuffer* sb, Line* line) {
    if (sb->count == sb->capacity) {
        // Overwrite oldest line
        free_line(&sb->lines[sb->head]);
        sb->head = (sb->head + 1) % sb->capacity;
        sb->count--;
    }

    sb->lines[sb->tail] = *line;
    sb->tail = (sb->tail + 1) % sb->capacity;
    sb->count++;
}

// Get line at viewport position (0 = bottom of scrollback)
Line* scrollback_get_line(ScrollbackBuffer* sb, size_t viewport_row) {
    size_t actual = sb->count - sb->scroll_offset - viewport_row - 1;
    if (actual >= sb->count) return NULL;

    size_t index = (sb->head + actual) % sb->capacity;
    return &sb->lines[index];
}

Advanced: Use memory-mapped file for infinite scrollback, compress old lines.

Learning milestones:

  1. Basic ring buffer works → Lines stored and retrieved correctly
  2. Scrolling works → Can view old content
  3. Memory bounded → Old lines discarded when limit reached
  4. Search works → Can find text in history
  5. Selection spans scrollback → Can select across viewport boundary

Project 7: VT100 State Machine

  • File: TERMINAL_EMULATOR_DEEP_DIVE_PROJECTS.md
  • Main Programming Language: C
  • Alternative Programming Languages: Rust, Zig
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 1. The “Resume Gold” (Educational/Personal Brand)
  • Difficulty: Level 3: Advanced (The Engineer)
  • Knowledge Area: State Machines / Terminal Emulation
  • Software or Tool: VT100 Emulator Core
  • Main Book: “Language Implementation Patterns” by Terence Parr

What you’ll build: A complete VT100/VT220/xterm-compatible terminal state machine that handles cursor movement, screen manipulation, modes, and all standard escape sequences.

Why it teaches terminal internals: This is the core of a real terminal emulator. Every sequence like “move cursor” or “clear screen” changes the terminal’s internal state. The VT100 standard, despite being from 1978, is still the basis of modern terminals.

Core challenges you’ll face:

  • Full escape sequence coverage → CSI, OSC, DCS, ESC, C0/C1
  • Screen buffer management → Primary and alternate screens
  • Cursor state → Position, visibility, saved positions
  • Mode flags → Origin mode, wrap mode, cursor mode
  • Tab stops → Programmable tab positions

Key Concepts:

Difficulty: Advanced Time estimate: 3-4 weeks Prerequisites: Projects 2, 5, 6

Real world outcome:

$ ./vt100_terminal

# Full-screen applications work!
$ vim file.txt     # ✓ Cursor movement, insert/command modes
$ htop             # ✓ Colors, cursor positioning, updates
$ tmux             # ✓ Alternate screen, mouse (partial)
$ less file.txt    # ✓ Scrolling, searching
$ man ls           # ✓ Bold, underlining

# Window title changes work
$ echo -e "\x1b]0;My Custom Title\x07"
# (Window title changes to "My Custom Title")

Implementation Hints:

typedef struct {
    // Screen buffers
    Cell* primary_screen;
    Cell* alternate_screen;
    Cell* active_screen;

    // Cursor
    int cursor_row, cursor_col;
    int saved_cursor_row, saved_cursor_col;
    CellAttributes cursor_attrs;
    bool cursor_visible;

    // Screen dimensions
    int rows, cols;

    // Mode flags
    bool origin_mode;          // DECOM
    bool auto_wrap_mode;       // DECAWM
    bool insert_mode;          // IRM
    bool cursor_keys_app_mode; // DECCKM
    bool alternate_screen_active;

    // Scrolling region
    int scroll_top, scroll_bottom;

    // Tab stops
    bool tab_stops[MAX_COLS];

    // Parser state
    Parser parser;

    // Scrollback
    ScrollbackBuffer scrollback;
} Terminal;

// Handle a parsed CSI sequence
void terminal_handle_csi(Terminal* t, char final, int* params, int count) {
    switch (final) {
    case 'A':  // CUU - Cursor Up
        t->cursor_row -= params[0] ?: 1;
        clamp_cursor(t);
        break;

    case 'H':  // CUP - Cursor Position
    case 'f':
        t->cursor_row = (params[0] ?: 1) - 1;
        t->cursor_col = (params[1] ?: 1) - 1;
        if (t->origin_mode) {
            t->cursor_row += t->scroll_top;
        }
        clamp_cursor(t);
        break;

    case 'J':  // ED - Erase in Display
        switch (params[0]) {
        case 0: erase_below(t); break;
        case 1: erase_above(t); break;
        case 2: erase_all(t); break;
        case 3: erase_scrollback(t); break;
        }
        break;

    case 'm':  // SGR - Select Graphic Rendition
        handle_sgr(t, params, count);
        break;

    case 'r':  // DECSTBM - Set Top and Bottom Margins
        t->scroll_top = (params[0] ?: 1) - 1;
        t->scroll_bottom = (params[1] ?: t->rows) - 1;
        break;

    // ... 50+ more cases
    }
}

Learning milestones:

  1. Cursor movement works → CUU, CUD, CUF, CUB, CUP
  2. Screen clearing works → ED, EL
  3. Scrolling region works → DECSTBM, scroll up/down
  4. Alternate screen works → vim, less switch cleanly
  5. vim is fully usable → Complete enough for real work

Project 8: Terminal Multiplexer (Mini-tmux)

  • File: TERMINAL_EMULATOR_DEEP_DIVE_PROJECTS.md
  • Main Programming Language: C
  • Alternative Programming Languages: Rust, Go
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 3: Advanced (The Engineer)
  • Knowledge Area: Multiplexing / Client-Server / PTY
  • Software or Tool: Terminal Multiplexer
  • Main Book: “Advanced Programming in the UNIX Environment” by Stevens & Rago

What you’ll build: A terminal multiplexer like tmux/screen that lets you detach/reattach sessions, with multiple panes in one terminal.

Why it teaches terminal internals: A multiplexer IS a terminal emulator! It sits between your real terminal and the shell, interpreting output, rendering to a virtual screen, and re-rendering that to the real terminal. Understanding tmux is understanding two levels of terminal emulation.

Core challenges you’ll face:

  • Client-server architecture → Detach/reattach requires separate processes
  • Multiple PTYs → Each pane has its own PTY
  • Virtual rendering → Render shells to buffers, composite to real terminal
  • Pane layout → Splitting, resizing, handling geometry
  • Re-rendering → Diff-based updates for efficiency

Key Concepts:

  • tmux Architecture: tmux source code (server.c, window.c, screen.c)
  • Unix Domain Sockets: “Advanced Programming in the UNIX Environment” Chapter 17 - Stevens & Rago
  • Virtual Terminal: How tmux Works

Difficulty: Advanced Time estimate: 4-6 weeks Prerequisites: Project 7

Real world outcome:

$ ./minitux new-session
# Creates a new session, attaches to it

$ ./minitux split-horizontal
┌─────────────────────────────────────┐
│ $ _                                 │
├─────────────────────────────────────┤
│ $ _                                 │
└─────────────────────────────────────┘

$ ./minitux detach
[detached from session 0]

# Close terminal, SSH again...

$ ./minitux attach
# Your session is restored!

Implementation Hints:

// Server: manages sessions, persists across detach
// Client: connects to server, displays output

// Session contains windows, windows contain panes
typedef struct Pane {
    Terminal terminal;    // Full terminal emulator!
    int master_fd;        // PTY master
    pid_t pid;            // Shell PID
    int x, y, w, h;       // Position in window
} Pane;

typedef struct Window {
    Pane** panes;
    int pane_count;
    int active_pane;
} Window;

typedef struct Session {
    Window** windows;
    int window_count;
    int active_window;
} Session;

// Server main loop
void server_main(int socket_fd) {
    while (1) {
        // Use poll/epoll on all:
        // - PTY masters (shell output)
        // - Client sockets (client input)
        // - Accept socket (new clients)

        poll(fds, nfds, -1);

        // Handle shell output -> update terminal state
        // Handle client input -> write to active pane's PTY
        // Handle new connections -> add client
    }
}

// Render all panes to virtual screen, send to client
void render_to_client(Session* s, int client_fd) {
    Window* w = s->windows[s->active_window];

    for (int i = 0; i < w->pane_count; i++) {
        Pane* p = w->panes[i];
        // Copy p->terminal cells to window buffer at (p->x, p->y)
        for (int row = 0; row < p->h; row++) {
            for (int col = 0; col < p->w; col++) {
                window_buffer[p->y + row][p->x + col] =
                    terminal_get_cell(&p->terminal, row, col);
            }
        }
    }

    // Send rendered buffer to client (with escape sequences for color)
    render_buffer_to_terminal(window_buffer, client_fd);
}

Learning milestones:

  1. Single pane works → Shell runs, output displays
  2. Detach/attach works → Server persists
  3. Horizontal split works → Two panes, correct sizing
  4. Vertical split works → Complex layouts
  5. Multiple windows work → Can switch between windows

Project 9: Font Rendering with FreeType

  • File: TERMINAL_EMULATOR_DEEP_DIVE_PROJECTS.md
  • Main Programming Language: C
  • Alternative Programming Languages: C++, Rust
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 3: Advanced (The Engineer)
  • Knowledge Area: Font Rendering / Graphics
  • Software or Tool: Font Renderer
  • Main Book: “Computer Graphics from Scratch” by Gabriel Gambetta

What you’ll build: A font rendering system using FreeType that rasterizes TrueType/OpenType fonts, with support for ligatures via HarfBuzz.

Why it teaches terminal internals: Terminal emulators must render text—fast. Understanding font rasterization, glyph caching, and text shaping explains why some terminals feel snappier and why font configuration is complex.

Core challenges you’ll face:

  • FreeType integration → Loading fonts, rasterizing glyphs
  • Glyph caching → Don’t re-rasterize the same character
  • Subpixel rendering → LCD antialiasing for sharpness
  • Unicode handling → Beyond ASCII, handling emoji
  • HarfBuzz for shaping → Ligatures, complex scripts

Key Concepts:

  • FreeType Tutorial: FreeType 2 Tutorial
  • Text Rendering: “Computer Graphics from Scratch” Chapter 8 - Gabriel Gambetta
  • HarfBuzz: HarfBuzz Manual
  • Font Metrics: Understanding font metrics (ascender, descender, advance)

Difficulty: Advanced Time estimate: 2-3 weeks Prerequisites: Basic graphics programming

Real world outcome:

$ ./font_render_demo --font="Fira Code" --size=14

[FONT] Loaded: Fira Code
[FONT] Has ligatures: Yes
[FONT] Glyph cache: 256 entries

Rendering: "Hello, World!"
  'H' (0x48) → glyph 43, advance 8px
  'e' (0x65) → glyph 72, advance 8px
  ...

Rendering with ligatures: "==> !="
  '=' '=' '>' → ligature glyph 512, advance 24px (3 chars)
  ' '
  '!' '=' → ligature glyph 489, advance 16px (2 chars)

Implementation Hints:

#include <ft2build.h>
#include FT_FREETYPE_H

typedef struct {
    FT_Library ft;
    FT_Face face;

    // Glyph cache
    struct {
        uint32_t codepoint;
        uint8_t* bitmap;
        int width, height;
        int bearing_x, bearing_y;
        int advance;
    } cache[256];

    int cell_width, cell_height;
} FontRenderer;

int font_init(FontRenderer* fr, const char* path, int size) {
    FT_Init_FreeType(&fr->ft);
    FT_New_Face(fr->ft, path, 0, &fr->face);
    FT_Set_Pixel_Sizes(fr->face, 0, size);

    // Calculate cell size from 'M' character
    FT_Load_Char(fr->face, 'M', FT_LOAD_RENDER);
    fr->cell_width = fr->face->glyph->advance.x >> 6;
    fr->cell_height = size * 1.2;  // With line spacing

    return 0;
}

uint8_t* font_render_glyph(FontRenderer* fr, uint32_t codepoint) {
    // Check cache first
    int idx = codepoint % 256;
    if (fr->cache[idx].codepoint == codepoint) {
        return fr->cache[idx].bitmap;
    }

    // Render glyph
    FT_Load_Char(fr->face, codepoint, FT_LOAD_RENDER);
    FT_GlyphSlot g = fr->face->glyph;

    // Copy to cache
    size_t size = g->bitmap.width * g->bitmap.rows;
    fr->cache[idx].bitmap = malloc(size);
    memcpy(fr->cache[idx].bitmap, g->bitmap.buffer, size);
    fr->cache[idx].codepoint = codepoint;
    fr->cache[idx].width = g->bitmap.width;
    fr->cache[idx].height = g->bitmap.rows;
    fr->cache[idx].bearing_x = g->bitmap_left;
    fr->cache[idx].bearing_y = g->bitmap_top;
    fr->cache[idx].advance = g->advance.x >> 6;

    return fr->cache[idx].bitmap;
}

Learning milestones:

  1. FreeType loads font → Basic setup works
  2. Single glyph renders → Can display one character
  3. String renders → Multiple characters with spacing
  4. Cache works → Performance acceptable
  5. Ligatures work → HarfBuzz integrated

Project 10: GPU-Accelerated Renderer

  • File: TERMINAL_EMULATOR_DEEP_DIVE_PROJECTS.md
  • Main Programming Language: C
  • Alternative Programming Languages: Rust, C++
  • Coolness Level: Level 5: Pure Magic (Super Cool)
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 4: Expert (The Systems Architect)
  • Knowledge Area: Graphics / GPU / OpenGL
  • Software or Tool: GPU Renderer
  • Main Book: “Computer Graphics from Scratch” by Gabriel Gambetta

What you’ll build: A GPU-accelerated text renderer using OpenGL (or Metal/Vulkan) that uploads glyph atlases to the GPU and renders the entire terminal in one draw call.

Why it teaches terminal internals: This is why Alacritty, Kitty, and Ghostty are fast. Instead of drawing each character with the CPU, they batch everything and let the GPU do parallel rendering. At 4K with 200+ rows of text, this makes a huge difference.

Core challenges you’ll face:

  • Texture atlas creation → Pack glyphs into a single texture
  • Vertex buffer generation → Build quads for each character
  • Shader programming → Vertex and fragment shaders for text
  • Color handling → Passing colors as attributes
  • Damage tracking → Only re-render changed cells

Key Concepts:

Difficulty: Expert Time estimate: 4-6 weeks Prerequisites: Project 9, basic OpenGL

Real world outcome:

$ ./gpu_terminal

[RENDERER] OpenGL 4.6
[RENDERER] GPU: NVIDIA GeForce RTX 3080
[RENDERER] Glyph atlas: 512x512, 256 glyphs
[RENDERER] Draw calls per frame: 1

# At 4K resolution, still buttery smooth
# Can handle 10,000+ cells at 144fps

Implementation Hints:

// Vertex shader
const char* vertex_shader = R"(
#version 330 core
layout (location = 0) in vec2 pos;      // Screen position
layout (location = 1) in vec2 tex;      // Texture coordinate
layout (location = 2) in vec4 fg_color;
layout (location = 3) in vec4 bg_color;

out vec2 TexCoords;
out vec4 FgColor;
out vec4 BgColor;

uniform mat4 projection;

void main() {
    gl_Position = projection * vec4(pos, 0.0, 1.0);
    TexCoords = tex;
    FgColor = fg_color;
    BgColor = bg_color;
}
)";

// Fragment shader
const char* fragment_shader = R"(
#version 330 core
in vec2 TexCoords;
in vec4 FgColor;
in vec4 BgColor;

out vec4 FragColor;

uniform sampler2D glyphAtlas;

void main() {
    float alpha = texture(glyphAtlas, TexCoords).r;
    FragColor = mix(BgColor, FgColor, alpha);
}
)";

// Build vertices for entire screen
void build_vertices(Terminal* t, Vertex* vertices) {
    int idx = 0;
    for (int row = 0; row < t->rows; row++) {
        for (int col = 0; col < t->cols; col++) {
            Cell* cell = terminal_get_cell(t, row, col);

            float x = col * cell_width;
            float y = row * cell_height;

            // Atlas coordinates for this glyph
            float u = (cell->codepoint % 16) / 16.0;
            float v = (cell->codepoint / 16) / 16.0;

            // 6 vertices for 2 triangles forming a quad
            vertices[idx++] = (Vertex){x, y, u, v, cell->fg, cell->bg};
            vertices[idx++] = (Vertex){x+cell_width, y, u+1/16.0, v, ...};
            // ... 4 more vertices
        }
    }
}

void render(Terminal* t) {
    build_vertices(t, vertex_buffer);
    glBufferData(GL_ARRAY_BUFFER, ...);
    glDrawArrays(GL_TRIANGLES, 0, t->rows * t->cols * 6);
}

Learning milestones:

  1. OpenGL context created → Window with GL works
  2. Glyph atlas uploaded → Texture on GPU
  3. One character renders → Shader pipeline works
  4. Full screen renders → All cells in one draw call
  5. 60fps at 4K → Actually fast

Project 11: Sixel/Image Protocol Support

  • File: TERMINAL_EMULATOR_DEEP_DIVE_PROJECTS.md
  • Main Programming Language: C
  • Alternative Programming Languages: Rust, Go
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 3: Advanced (The Engineer)
  • Knowledge Area: Graphics Protocols / Terminal Extensions
  • Software or Tool: Image Terminal
  • Main Book: N/A (protocol specifications)

What you’ll build: Support for inline images in your terminal using Sixel graphics, Kitty graphics protocol, or iTerm2 inline images.

Why it teaches terminal internals: Modern terminals can display images! Understanding these protocols shows how terminals extend beyond text, and the trade-offs between different approaches.

Core challenges you’ll face:

  • Sixel parsing → Old DEC standard, still widely supported
  • Kitty protocol → Modern, efficient, uses base64 + chunks
  • iTerm2 protocol → OSC-based, proprietary but common
  • Image placement → Fitting images into cell grid
  • Memory management → Images can be large

Key Concepts:

Difficulty: Advanced Time estimate: 2-3 weeks Prerequisites: Project 7

Real world outcome:

$ ./image_terminal

# Display image using kitty protocol
$ kitty +kitten icat photo.png
[IMAGE] Received 1920x1080 image via Kitty protocol
[IMAGE] Scaling to 80x24 cells
# Image displays in terminal!

# Or sixel
$ img2sixel photo.png
[IMAGE] Parsing Sixel data...
[IMAGE] 320x200 image rendered

Implementation Hints:

// Kitty graphics protocol: ESC_Ga=T,f=100,s=width,v=height;base64_data\ESC\

typedef struct {
    uint32_t id;          // Image ID for references
    uint8_t* pixels;      // RGBA data
    int width, height;
    int cell_x, cell_y;   // Position in cells
    int cell_w, cell_h;   // Size in cells
} InlineImage;

void handle_kitty_graphics(Terminal* t, char* payload) {
    // Parse key=value pairs before semicolon
    // a=T means "transmit data"
    // f=100 means PNG format
    // s=width, v=height

    char* data = strchr(payload, ';');
    if (!data) return;
    data++;

    // Decode base64
    size_t decoded_len;
    uint8_t* decoded = base64_decode(data, &decoded_len);

    // If PNG, decode with libpng
    InlineImage* img = decode_png(decoded, decoded_len);

    // Add to image list at current cursor position
    img->cell_x = t->cursor_col;
    img->cell_y = t->cursor_row;
    img->cell_w = (img->width + t->cell_width - 1) / t->cell_width;
    img->cell_h = (img->height + t->cell_height - 1) / t->cell_height;

    add_image(t, img);
}

Learning milestones:

  1. Kitty protocol parsed → Escape sequence recognized
  2. Base64 decoded → Image data extracted
  3. PNG decoded → Pixel data available
  4. Image renders → Displays in terminal
  5. Scrolling works → Images scroll with text

  • File: TERMINAL_EMULATOR_DEEP_DIVE_PROJECTS.md
  • Main Programming Language: C
  • Alternative Programming Languages: Rust, Go
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 2: Intermediate (The Developer)
  • Knowledge Area: Terminal Extensions / OSC
  • Software or Tool: OSC Handler
  • Main Book: N/A (xterm documentation)

What you’ll build: Support for OSC (Operating System Command) sequences that enable clipboard integration, clickable hyperlinks, and window title changes.

Why it teaches terminal internals: OSC sequences are how terminals communicate with the desktop environment. Clipboard access, hyperlinks, and notifications all use OSC.

Core challenges you’ll face:

  • OSC parsing → Different format than CSI
  • Clipboard integration → Reading and writing system clipboard
  • Hyperlink detection → OSC 8 protocol
  • Security concerns → Clipboard can be exploited!
  • Window title → OSC 0, 1, 2

Key Concepts:

Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Project 7

Real world outcome:

$ ./osc_terminal

# Window title
$ echo -e "\x1b]0;My Terminal Title\x07"
# Title bar changes!

# Clipboard (read)
$ printf "\x1b]52;c;?\x07"
# Terminal responds with clipboard contents in base64

# Clipboard (write)
$ printf "\x1b]52;c;$(echo -n "Hello" | base64)\x07"
# "Hello" is now in clipboard!

# Hyperlinks
$ echo -e "\x1b]8;;https://example.com\x07Click Here\x1b]8;;\x07"
# "Click Here" is a clickable link!

Implementation Hints:

void handle_osc(Terminal* t, int code, char* data) {
    switch (code) {
    case 0:  // Window title + icon
    case 2:  // Window title
        set_window_title(t, data);
        break;

    case 52:  // Clipboard manipulation
        // Format: Pc;Pd where Pc is clipboard name, Pd is data
        char* semicolon = strchr(data, ';');
        if (!semicolon) break;

        char clipboard = data[0];  // 'c' for clipboard, 'p' for primary
        char* content = semicolon + 1;

        if (strcmp(content, "?") == 0) {
            // Query clipboard
            char* clip_data = get_clipboard();
            char* b64 = base64_encode(clip_data);
            // Send response: OSC 52 ; c ; base64_data ST
            write_to_master(t, "\x1b]52;c;");
            write_to_master(t, b64);
            write_to_master(t, "\x07");
        } else {
            // Set clipboard
            char* decoded = base64_decode(content);
            set_clipboard(decoded);
        }
        break;

    case 8:  // Hyperlinks
        // Format: params;uri
        // params can include id=xxx
        char* uri_start = strchr(data, ';');
        if (!uri_start) break;
        uri_start++;

        if (*uri_start) {
            // Start hyperlink
            t->current_hyperlink = strdup(uri_start);
        } else {
            // End hyperlink
            free(t->current_hyperlink);
            t->current_hyperlink = NULL;
        }
        break;
    }
}

Learning milestones:

  1. OSC parsed correctly → Sequences recognized
  2. Window title works → Title bar changes
  3. Clipboard write works → Can set clipboard
  4. Clipboard read works → Can query clipboard
  5. Hyperlinks work → Ctrl+click opens browser

Project 13: Full Terminal Emulator

  • File: TERMINAL_EMULATOR_DEEP_DIVE_PROJECTS.md
  • Main Programming Language: C
  • Alternative Programming Languages: Rust, Zig
  • Coolness Level: Level 5: Pure Magic (Super Cool)
  • Business Potential: 4. The “Open Core” Infrastructure (Enterprise Scale)
  • Difficulty: Level 4: Expert (The Systems Architect)
  • Knowledge Area: Complete Terminal Implementation
  • Software or Tool: Complete Terminal Emulator
  • Main Book: Multiple (all previous)

What you’ll build: Integrate all previous projects into a fully functional terminal emulator with a GUI, running as a standalone application.

Why it teaches terminal internals: This is the culmination—a real terminal emulator you can use daily. You’ll understand how iTerm2, Ghostty, and Alacritty are built because you built one yourself.

Core challenges you’ll face:

  • GUI integration → SDL2, GTK, Qt, or native (Cocoa/Win32)
  • Event loop design → Handle input, output, resize, signals
  • Configuration → Parsing config files
  • Tabs/splits → Window management
  • Selection and clipboard → Mouse interaction

Key Concepts:

  • All previous projects combined
  • GUI Toolkits: SDL2, GTK, Qt documentation
  • Real Terminal Sources: Alacritty, Kitty, WezTerm source code

Difficulty: Expert Time estimate: 8-12 weeks Prerequisites: All projects 1-12

Real world outcome:

$ ./myterm

# It's a real terminal emulator!
# - Type commands
# - Use vim, htop, tmux
# - Full color support
# - Clickable links
# - Copy/paste works
# - Font rendering is good
# - GPU accelerated (smooth scrolling)
# - Scrollback history
# - Configurable via ~/.mytermrc

Implementation Hints:

// Main application structure
typedef struct {
    // Window/GUI
    SDL_Window* window;
    SDL_Renderer* renderer;  // Or OpenGL context

    // Terminal core
    Terminal terminal;

    // PTY
    int pty_master;
    pid_t shell_pid;

    // Font
    FontRenderer font;

    // Configuration
    Config config;

    // Selection state
    Selection selection;

    // Scrollback
    ScrollbackBuffer scrollback;
} TerminalApp;

void main_loop(TerminalApp* app) {
    while (running) {
        // Handle window events (keyboard, mouse, resize)
        while (SDL_PollEvent(&event)) {
            handle_event(app, &event);
        }

        // Check for PTY output
        if (poll_pty(app->pty_master, 0)) {
            char buf[4096];
            ssize_t n = read(app->pty_master, buf, sizeof(buf));
            if (n > 0) {
                for (ssize_t i = 0; i < n; i++) {
                    terminal_feed(&app->terminal, buf[i]);
                }
            }
        }

        // Render if needed
        if (terminal_is_dirty(&app->terminal)) {
            render(app);
            SDL_RenderPresent(app->renderer);
        }

        // Limit to ~60fps
        SDL_Delay(16);
    }
}

Learning milestones:

  1. Window opens → GUI works
  2. Shell starts → PTY connected
  3. Characters display → Basic rendering
  4. vim works → Full VT100 compatibility
  5. Daily driver ready → You use it for real work!

Project 14: Web Terminal (xterm.js Backend)

  • File: TERMINAL_EMULATOR_DEEP_DIVE_PROJECTS.md
  • Main Programming Language: JavaScript/TypeScript + Go
  • Alternative Programming Languages: Python, Rust (backend)
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 4. The “Open Core” Infrastructure (Enterprise Scale)
  • Difficulty: Level 3: Advanced (The Engineer)
  • Knowledge Area: WebSockets / Terminal Over Network
  • Software or Tool: Web Terminal
  • Main Book: N/A (xterm.js docs + WebSocket RFC)

What you’ll build: A web-based terminal using xterm.js in the browser and a Go/Python backend that manages PTY connections.

Why it teaches terminal internals: Web terminals are everywhere—cloud consoles, Jupyter, VS Code. Understanding how they work (WebSocket + PTY bridging) opens up a whole category of applications.

Core challenges you’ll face:

  • WebSocket protocol → Bidirectional communication
  • PTY management on server → Multiple concurrent sessions
  • Resize handling → SIGWINCH over network
  • Authentication → Secure access to shell
  • Latency → Making it feel responsive

Key Concepts:

Difficulty: Advanced Time estimate: 2-3 weeks Prerequisites: Basic web development, Projects 1-4

Real world outcome:

$ ./webterm --port 8080

[WEBTERM] Listening on http://localhost:8080
[WEBTERM] Open browser to access terminal

# In browser at localhost:8080
# Full terminal in the browser!
# Can run vim, htop, ssh
# Multiple tabs supported

Implementation Hints:

// Go backend
package main

import (
    "github.com/creack/pty"
    "github.com/gorilla/websocket"
)

func handleWebSocket(w http.ResponseWriter, r *http.Request) {
    // Upgrade to WebSocket
    conn, _ := upgrader.Upgrade(w, r, nil)
    defer conn.Close()

    // Create PTY
    cmd := exec.Command("/bin/bash")
    ptmx, _ := pty.Start(cmd)
    defer ptmx.Close()

    // PTY output -> WebSocket
    go func() {
        buf := make([]byte, 4096)
        for {
            n, _ := ptmx.Read(buf)
            conn.WriteMessage(websocket.BinaryMessage, buf[:n])
        }
    }()

    // WebSocket input -> PTY
    for {
        _, msg, _ := conn.ReadMessage()
        ptmx.Write(msg)
    }
}
// Frontend (xterm.js)
import { Terminal } from 'xterm';
import { AttachAddon } from 'xterm-addon-attach';
import { FitAddon } from 'xterm-addon-fit';

const term = new Terminal();
const fitAddon = new FitAddon();
term.loadAddon(fitAddon);
term.open(document.getElementById('terminal'));
fitAddon.fit();

const socket = new WebSocket('ws://localhost:8080/ws');
const attachAddon = new AttachAddon(socket);
term.loadAddon(attachAddon);

// Handle resize
window.addEventListener('resize', () => {
    fitAddon.fit();
    socket.send(JSON.stringify({
        type: 'resize',
        cols: term.cols,
        rows: term.rows
    }));
});

Learning milestones:

  1. WebSocket connects → Handshake works
  2. Keystrokes reach shell → Input path works
  3. Output displays → Output path works
  4. Resize works → SIGWINCH sent over network
  5. Multiple sessions → Concurrent users

Project 15: Feature-Complete Terminal (Capstone)

  • File: TERMINAL_EMULATOR_DEEP_DIVE_PROJECTS.md
  • Main Programming Language: Rust or Zig
  • Alternative Programming Languages: C++, C
  • Coolness Level: Level 5: Pure Magic (Super Cool)
  • Business Potential: 5. The “Industry Disruptor” (VC-Backable Platform)
  • Difficulty: Level 5: Master (The First-Principles Wizard)
  • Knowledge Area: Complete Terminal Engineering
  • Software or Tool: Production Terminal
  • Main Book: All previous + terminal source code study

What you’ll build: A feature-complete, polished terminal emulator comparable to Alacritty or Kitty—GPU-accelerated, with tabs, configuration, and all modern features.

Why it teaches terminal internals: This is mastery. You’ll have built something that rivals commercial products, understanding every layer from the kernel PTY interface to GPU shader programming.

Core challenges you’ll face:

  • Polish → Handling all edge cases
  • Performance → Optimizing for 144fps at 4K
  • Compatibility → All xterm sequences
  • Features → Splits, tabs, search, notifications
  • Cross-platform → Linux, macOS, Windows

Key Concepts:

  • Everything from Projects 1-14
  • Study real terminals: Ghostty, Alacritty, Kitty, WezTerm source

Difficulty: Master Time estimate: 6-12 months Prerequisites: All previous projects

Real world outcome: A terminal you publish on GitHub that gets stars and users.

Features:

  • GPU-accelerated rendering (OpenGL/Metal/Vulkan)
  • Full VT100/VT220/xterm compatibility
  • Tabs and splits with keyboard navigation
  • Scrollback with search
  • Hyperlinks and OSC 52 clipboard
  • Inline images (Kitty protocol)
  • Configurable via TOML/Lua
  • Themes and font configuration
  • Ligature support
  • Unicode/emoji support
  • Mouse support (selection, scroll, buttons)
  • Cross-platform (at least Linux + macOS)

Project Comparison Table

# Project Difficulty Time Understanding Fun
1 PTY Explorer Intermediate 1 week ⭐⭐⭐ ⭐⭐⭐
2 Escape Parser Intermediate 1-2 weeks ⭐⭐⭐⭐ ⭐⭐⭐
3 termios Explorer Beginner 3-5 days ⭐⭐⭐ ⭐⭐⭐
4 Minimal Terminal Intermediate 3-5 days ⭐⭐⭐⭐ ⭐⭐⭐⭐
5 Color Renderer Intermediate 1 week ⭐⭐⭐ ⭐⭐⭐⭐
6 Scrollback Buffer Advanced 2 weeks ⭐⭐⭐⭐ ⭐⭐⭐
7 VT100 State Machine Advanced 3-4 weeks ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐
8 Mini-tmux Advanced 4-6 weeks ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
9 Font Rendering Advanced 2-3 weeks ⭐⭐⭐ ⭐⭐⭐
10 GPU Renderer Expert 4-6 weeks ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
11 Image Protocol Advanced 2-3 weeks ⭐⭐⭐ ⭐⭐⭐⭐
12 OSC Sequences Intermediate 1-2 weeks ⭐⭐⭐ ⭐⭐⭐
13 Full Terminal Expert 8-12 weeks ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
14 Web Terminal Advanced 2-3 weeks ⭐⭐⭐⭐ ⭐⭐⭐⭐
15 Production Terminal Master 6-12 months ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐

Quick Understanding (2-4 weeks):

Projects 1 → 3 → 4 → 2

Build a Working Terminal (2-3 months):

Projects 1 → 2 → 3 → 4 → 5 → 7 → 13

Deep Mastery (6-12 months):

All projects in order

Want to Build a tmux Alternative:

Projects 1 → 2 → 4 → 7 → 8

Want to Build a Web Terminal:

Projects 1 → 2 → 4 → 14


Essential Resources

Books

  1. “The Linux Programming Interface” by Michael Kerrisk - Chapter 64 (PTY), 34 (Sessions)
  2. “Advanced Programming in the UNIX Environment” by Stevens & Rago - Chapters 9, 18
  3. “Language Implementation Patterns” by Terence Parr - For parser design
  4. “Computer Graphics from Scratch” by Gabriel Gambetta - For rendering

Online Resources

  1. Anatomy of a Terminal Emulator - Excellent overview
  2. VT100.net - Original DEC documentation
  3. xterm Control Sequences - Definitive reference
  4. Build Your Own Text Editor - Great termios tutorial

Source Code to Study

  1. Ghostty - Zig, excellent architecture
  2. Alacritty - Rust, minimal and fast
  3. Kitty - C + Python, feature-rich
  4. WezTerm - Rust, very complete

Summary

# Project Name Main Language
1 PTY Explorer Tool C
2 Escape Sequence Parser C
3 termios Mode Experimenter C
4 Minimal Terminal Emulator C
5 ANSI Color Renderer C
6 Scrollback Buffer Implementation C
7 VT100 State Machine C
8 Terminal Multiplexer (Mini-tmux) C
9 Font Rendering with FreeType C
10 GPU-Accelerated Renderer C
11 Sixel/Image Protocol Support C
12 OSC Sequences (Clipboard, Hyperlinks) C
13 Full Terminal Emulator C
14 Web Terminal (xterm.js Backend) Go + JavaScript
15 Feature-Complete Terminal (Capstone) Rust or Zig

Answering Your Original Questions

How do terminals like iTerm2, Ghostty, Alacritty work?

They all follow the same pattern:

  1. Create a PTY pair (master/slave)
  2. Fork a shell process attached to the slave
  3. Read bytes from PTY master, parse escape sequences, update internal state
  4. Render that state to the screen (CPU or GPU)
  5. Read keyboard input, write to PTY master

The differences are in:

  • Rendering technology (CPU vs GPU)
  • Features (tabs, splits, images)
  • Language/architecture (monolithic vs layered)
  • Configuration (GUI vs file)

What differs between them?

Aspect Traditional (iTerm2) Modern (Ghostty, Alacritty)
Rendering CPU-based GPU-accelerated
Performance OK at 1080p Great at 4K+
Memory Can be heavy Usually lightweight
Features Full-featured Varies (Alacritty minimal)
Philosophy Native Mac experience Cross-platform core

Why are terminals needed?

Terminals provide:

  1. Text-based interface to the operating system
  2. Multiplexing of multiple programs in one window
  3. Historical compatibility with decades of software
  4. Efficiency for power users (keyboard-driven)
  5. Remote access foundation (SSH sends terminal data)

Can you interact with shell without terminal?

Yes!

  • Pipes: For non-interactive programs (echo "ls" | bash)
  • expect/pexpect: For automating interactive programs
  • PTY libraries: For programmatic terminal access
  • SSH -T: Run commands without terminal allocation
  • Plain sockets: For programs that don’t need TTY

But many programs require a TTY because:

  • They check isatty() to enable features
  • They bypass stdout for passwords
  • They use raw mode for UI

After completing these projects, you’ll understand why vim feels different from cat, why Ctrl+C sometimes doesn’t work, how tmux can detach sessions, and why GPU terminals are smoother. You’ll have built what millions of developers use every day.