Project 8: Terminal Multiplexer (Mini-tmux)

Build a minimal terminal multiplexer that manages multiple PTYs, panes, and sessions with basic input routing.

Quick Reference

Attribute Value
Difficulty Level 3: Advanced
Time Estimate 2-4 weeks
Main Programming Language C (Alternatives: Rust, Go)
Alternative Programming Languages Rust, Go
Coolness Level Level 4: Hardcore Tech Flex
Business Potential Level 2: Open Source Builder
Prerequisites PTY basics, screen model, event loop
Key Topics multiplexing, pane layout, input routing

1. Learning Objectives

By completing this project, you will:

  1. Create and manage multiple PTY sessions concurrently.
  2. Route keyboard input to the active pane reliably.
  3. Render multiple panes into a single composite screen.
  4. Implement basic pane splits and focus changes.
  5. Handle resize propagation to each PTY.

2. All Theory Needed (Per-Concept Breakdown)

Concept 1: PTY Layering and Session Management

Fundamentals

A terminal multiplexer owns multiple PTY masters, one per pane. Each PTY slave hosts a shell or application. The multiplexer reads output from each PTY master and renders a composite screen. It also exposes a single PTY to the outer terminal (or renders directly). This layering allows you to detach and reattach sessions, and it explains why multiplexers can keep programs running in the background.

Deep Dive into the Concept

Multiplexers like tmux act as a terminal emulator for each pane while also acting as an application to the user’s terminal. For each pane, the multiplexer creates a PTY pair and spawns a shell connected to the slave. The multiplexer reads from the master, parses output, updates a per-pane screen model, and then composes all panes into a single display. This creates two layers of terminal emulation: the pane layer and the outer layer.

Session management is the feature that makes multiplexers powerful. A session is a collection of panes and their associated PTYs. When the client disconnects (e.g., you close the terminal), the multiplexer continues to run, holding PTY masters open so the child processes remain active. When you reconnect, the multiplexer reattaches the UI to the existing session and continues rendering from the stored screen models.

This project implements a minimal subset: one session, multiple panes, no networked clients. Still, you must follow the same invariants: each pane has its own PTY and its own screen model. The multiplexer is responsible for resizing each PTY when pane dimensions change, which requires calling TIOCSWINSZ per pane. If you skip this, programs inside panes will think they have the wrong size and render incorrectly.

Input routing is also a critical concept. The multiplexer must capture user input and send it to the active pane’s PTY master. Control keys for the multiplexer (e.g., a prefix like Ctrl+B) should be intercepted and not forwarded to the pane. This requires a mode switch between “command mode” and “passthrough mode”. Even a minimal multiplexer benefits from a prefix key to switch panes and split windows.

How this fits on projects

This concept is central to this project and reused in P13 and P14.

Definitions & Key Terms

  • Pane -> a sub-rectangle of the terminal UI tied to a PTY.
  • Session -> a collection of panes and state that persists.
  • Prefix key -> key combo used to trigger multiplexer commands.
  • Detach -> disconnect UI while session continues.

Mental Model Diagram (ASCII)

User Terminal
     |
     v
[Multiplexer UI]
  |         |
  v         v
PTY master  PTY master
  |           |
PTY slave   PTY slave
  |           |
Shell A     Shell B

How It Works (Step-by-Step)

  1. Create a PTY per pane and spawn a shell.
  2. Maintain a screen model per pane.
  3. Composite panes into one display for the user.
  4. Route input to the active pane or handle command keys.

Invariants:

  • Each pane has its own PTY and screen state.
  • Resize signals are sent to each PTY when pane sizes change.

Failure modes:

  • Sharing one PTY across panes causes mixed output.
  • Missing resize propagation breaks full-screen apps.

Minimal Concrete Example

Pane p = pane_create(cols, rows);
pty_spawn(&p.pty, shell_path);

Common Misconceptions

  • “Multiplexers just split the screen.” -> They manage separate PTYs.
  • “Resize is only visual.” -> It must propagate to each PTY.
  • “Input routing is trivial.” -> Prefix keys must be intercepted.

Check-Your-Understanding Questions

  1. Why does each pane need its own PTY?
  2. What happens if you do not send SIGWINCH to a pane?
  3. Why are prefix keys necessary?

Check-Your-Understanding Answers

  1. To isolate programs and screen state per pane.
  2. Programs will render at the wrong size.
  3. To distinguish multiplexer commands from app input.

Real-World Applications

  • tmux and screen
  • IDE split terminals

Where You’ll Apply It

References

  • tmux architecture docs
  • pty(7) and termios(3) man pages

Key Insight

A multiplexer is a PTY farm plus a compositor.

Summary

Correct PTY layering is the foundation for panes and session persistence.

Homework/Exercises to Practice the Concept

  1. Spawn two PTYs and write different text to each.
  2. Resize a pane and verify stty size inside it.
  3. Implement a prefix key and show it is not sent to the app.

Solutions to the Homework/Exercises

  1. Use two child shells and log output separately.
  2. Call TIOCSWINSZ and check stty size.
  3. Intercept prefix and print a status message instead.

Concept 2: Layout, Compositing, and Input Routing

Fundamentals

A multiplexer must divide the screen into panes and render each pane’s screen into the correct region. Layout can be simple (horizontal/vertical split) or complex (tree). Input routing determines which pane receives user keystrokes. Both are core to usability.

Deep Dive into the Concept

A basic layout engine can be implemented with a binary split tree. Each node represents a rectangle and is either a leaf (pane) or split (horizontal/vertical) into two children. The geometry of each pane can be computed by recursively dividing the parent rectangle. This makes resizing straightforward: changing the root size propagates to all leaves. For a minimal multiplexer, support only two splits and fixed ratios (e.g., 50/50).

Compositing requires rendering each pane’s screen model into a shared buffer. You can create a “composite screen” and copy each pane’s cells into its region. You may also draw borders to separate panes. If you render directly to the terminal, you can draw each pane by moving the cursor to the correct positions and printing rows. The main invariant is that pane content must not overwrite other pane regions.

Input routing is primarily about focus. Only the active pane receives user keystrokes; other panes continue running but do not receive input. The multiplexer should provide a command to switch focus, such as a prefix key + arrow. When you switch, update a visual indicator (highlighted border or status line) so the user knows which pane is active.

Handling resize is tricky. When the outer terminal resizes, you must recompute pane rectangles and send TIOCSWINSZ to each PTY. This can trigger SIGWINCH in each child app, which should then redraw correctly in its pane. If you forget this step, programs inside panes will still think they have the old size.

How this fits on projects

This concept is reused in P13 and P15 for complex UI features.

Definitions & Key Terms

  • Layout tree -> hierarchy of splits defining pane geometry.
  • Compositing -> combining multiple pane buffers into one.
  • Focus -> which pane receives input.

Mental Model Diagram (ASCII)

+---------------------------+
| pane A        | pane B    |
|               |           |
+---------------+-----------+

How It Works (Step-by-Step)

  1. Compute pane rectangles from layout tree.
  2. Render each pane’s buffer into composite buffer.
  3. Draw borders and status line.
  4. Route input to active pane.

Invariants:

  • Pane rectangles cover the screen without overlap.
  • Active pane is always well-defined.

Failure modes:

  • Overlapping panes cause visual corruption.
  • Focus not updated leads to input going to wrong pane.

Minimal Concrete Example

if (key == PREFIX) mode = COMMAND;
else if (mode == COMMAND && key == 'n') focus_next_pane();
else write(active_pane.pty_master, &key, 1);

Common Misconceptions

  • “Layout is just a split line.” -> It is a tree of rectangles.
  • “All panes get input.” -> Only the active pane should.

Check-Your-Understanding Questions

  1. Why use a layout tree instead of a flat list?
  2. How do you ensure panes do not overlap?
  3. What happens if you send input to the wrong pane?

Check-Your-Understanding Answers

  1. It scales to nested splits and dynamic resizing.
  2. Compute rectangles from a tree and enforce boundaries.
  3. Commands affect the wrong app and confuse the user.

Real-World Applications

  • tmux and screen layouts
  • IDE split panes

Where You’ll Apply It

References

  • tmux source code layout tree
  • UI layout algorithms

Key Insight

A multiplexer is a UI compositor plus an input router.

Summary

Correct pane layout and routing are the difference between a toy and a usable multiplexer.

Homework/Exercises to Practice the Concept

  1. Implement a fixed 2-pane split and draw borders.
  2. Add a focus switch key and highlight the active pane.
  3. Resize the terminal and recompute pane rectangles.

Solutions to the Homework/Exercises

  1. Divide width in half and render two buffers.
  2. Add a status line or bold border around active pane.
  3. On SIGWINCH, recalc layout and send TIOCSWINSZ.

3. Project Specification

3.1 What You Will Build

A mini tmux-like multiplexer that:

  • Manages multiple PTYs and panes.
  • Splits the screen horizontally or vertically.
  • Routes input to the focused pane using a prefix key.
  • Renders panes with borders and a status line.

Intentionally excluded:

  • Remote clients, session persistence across restarts.

3.2 Functional Requirements

  1. Pane creation: spawn PTY per pane.
  2. Layout: split screen into 2 panes (horizontal/vertical).
  3. Input routing: prefix key for commands and focus switching.
  4. Rendering: composite panes and draw borders.
  5. Resize: propagate new sizes to each pane PTY.

3.3 Non-Functional Requirements

  • Responsiveness: input latency under 50 ms.
  • Correctness: each pane receives only its own output.
  • Determinism: replayable logs for tests.

3.4 Example Usage / Output

$ ./mini_mux
[prefix] Ctrl+B, then % to split, then o to switch

3.5 Data Formats / Schemas / Protocols

  • Layout tree: nodes {split, ratio, left, right}.

3.6 Edge Cases

  • Split with very small panes.
  • Resizing while panes are running full-screen apps.
  • Child process exits in one pane.

3.7 Real World Outcome

A working mini-multiplexer that runs two shells side by side.

3.7.1 How to Run (Copy/Paste)

cc -O2 -o mini_mux mini_mux.c
TZ=UTC LC_ALL=C ./mini_mux

3.7.2 Golden Path Demo (Deterministic)

  1. Start with two panes and run ls in each.
  2. Verify each pane shows its own output and focus indicator switches.

3.7.3 Failure Demo (Deterministic)

$ ./mini_mux --split 1x0
error: pane size too small
exit status: 64

3.7.4 If TUI: ASCII layout

+---------------+---------------+
| pane 1        | pane 2        |
| $ ls          | $ htop        |
| ...           | ...           |
+---------------+---------------+

4. Solution Architecture

4.1 High-Level Design

PTYs -> per-pane screen -> compositor -> stdout
           ^                 |
           |                 v
         input router <--- keyboard

4.2 Key Components

Component Responsibility Key Decisions
Pane Manager Create/track PTYs One PTY per pane
Layout Engine Compute pane geometry Simple split tree
Compositor Draw panes Composite buffer
Input Router Send keys to active pane Prefix command mode

4.3 Data Structures (No Full Code)

struct Pane { Pty pty; Screen screen; Rect rect; };
struct LayoutNode { bool split; int ratio; int left, right; };

4.4 Algorithm Overview

Key Algorithm: Render Composite

  1. Clear composite buffer.
  2. For each pane, copy its cells into its rectangle.
  3. Draw borders and status line.

Complexity Analysis:

  • Time: O(total cells)
  • Space: O(screen size)

5. Implementation Guide

5.1 Development Environment Setup

cc --version

5.2 Project Structure

mini-mux/
|-- src/
|   |-- mux.c
|   |-- layout.c
|   `-- compositor.c
|-- tests/
|   `-- layout_tests.c
|-- Makefile
`-- README.md

5.3 The Core Question You’re Answering

“How do you manage multiple PTYs and compose them into one terminal view?”

5.4 Concepts You Must Understand First

  1. PTY layering and session management.
  2. Pane layout and compositing.
  3. Input routing and focus.

5.5 Questions to Guide Your Design

  1. How will you represent the layout tree?
  2. How will you signal focus changes to the user?
  3. How will you resize panes consistently?

5.6 Thinking Exercise

Design a keybinding scheme that avoids conflicting with normal app input.

5.7 The Interview Questions They’ll Ask

  1. Why does each pane need a separate PTY?
  2. How do you handle resizing in a multiplexer?
  3. How do you avoid input going to the wrong pane?

5.8 Hints in Layers

Hint 1: Start with two panes Hardcode a split and get it working.

Hint 2: Use a prefix key Intercept commands before forwarding.

Hint 3: Send SIGWINCH to each PTY Call TIOCSWINSZ on resize.

Hint 4: Composite into a buffer Render each pane into a shared grid.

5.9 Books That Will Help

Topic Book Chapter
Terminal internals “The Linux Programming Interface” Ch. 62-64
UI design “Clean Architecture” Ch. 7

5.10 Implementation Phases

Phase 1: PTY per pane (1 week)

Goals: spawn two PTYs and read output. Tasks:

  1. Create two PTY sessions.
  2. Capture output into separate buffers. Checkpoint: Two shells run simultaneously.

Phase 2: Layout and rendering (1 week)

Goals: split screen and render panes. Tasks:

  1. Implement fixed split layout.
  2. Draw borders and status line. Checkpoint: Panes render side by side.

Phase 3: Input routing (1 week)

Goals: prefix command mode and focus. Tasks:

  1. Implement prefix key.
  2. Add focus switching. Checkpoint: Input goes to selected pane.

5.11 Key Implementation Decisions

Decision Options Recommendation Rationale
Layout Fixed split vs tree Tree Extensible
Input routing Always active vs prefix Prefix Avoid conflicts
Rendering Per-pane vs composite Composite Single output stream

6. Testing Strategy

6.1 Test Categories

Category Purpose Examples
Unit Tests Layout calculation Split 80x24
Integration Tests Pane output Two shells
Edge Case Tests Tiny panes 1x1 split

6.2 Critical Test Cases

  1. Focus: input goes only to active pane.
  2. Resize: pane sizes update and PTYs receive SIGWINCH.
  3. Isolation: output from one pane never appears in the other.

6.3 Test Data

Pane A runs: echo A
Pane B runs: echo B
Expected: A appears only in pane A

7. Common Pitfalls & Debugging

7.1 Frequent Mistakes

Pitfall Symptom Solution
Shared PTY Mixed output One PTY per pane
No resize propagation Full-screen apps break Send TIOCSWINSZ
No focus indicator Confusing input Draw borders/status

7.2 Debugging Strategies

  • Log pane focus changes and input routing.
  • Render pane IDs on screen for clarity.

7.3 Performance Traps

Full redraws are fine for small panes, but use damage tracking later.


8. Extensions & Challenges

8.1 Beginner Extensions

  • Add pane titles.
  • Add a status bar with clock and session name.

8.2 Intermediate Extensions

  • Add multiple sessions.
  • Implement pane zoom mode.

8.3 Advanced Extensions

  • Add client/server model for detach/attach.
  • Add scripting for layout presets.

9. Real-World Connections

9.1 Industry Applications

  • tmux and screen in dev workflows
  • Remote session persistence
  • tmux: full-featured multiplexer
  • screen: classic multiplexer

9.3 Interview Relevance

  • PTY management at scale
  • UI compositing and routing

10. Resources

10.1 Essential Reading

  • tmux user manual and design notes
  • pty(7) and ioctl(TIOCSWINSZ) docs

10.2 Video Resources

  • Conference talks on tmux internals

10.3 Tools & Documentation

  • tmux source code for reference

11. Self-Assessment Checklist

11.1 Understanding

  • I can explain why each pane needs a PTY.
  • I can describe how layout trees work.
  • I can explain input routing.

11.2 Implementation

  • Panes render correctly without overlap.
  • Focus switching works reliably.
  • Resize events propagate to all panes.

11.3 Growth

  • I can extend to multiple sessions.
  • I can design a client/server split.

12. Submission / Completion Criteria

Minimum Viable Completion:

  • Two panes with independent PTYs and focus switching.
  • Correct rendering and resize behavior.

Full Completion:

  • Stable prefix command mode and status line.
  • Deterministic integration tests.

Excellence (Going Above & Beyond):

  • Detach/attach support with client/server.
  • Layout presets and scripting.