Terminal stack — how it fits together¶
Five-minute read: where things live, who owns what, and how bytes move. No methodology—just wiring.
Scope: This file is only the graphical terminal slice (dock → gterm → PTY → dps → pixels). For the whole OS at high level (correct names + layered diagram), see system-architecture.md. For the kernel + syscall contract around PTY blocking and flags, see pty-v1-design.md.
1. One diagram — terminal subsystem only (layers + PTY)¶
This is the main byte spine for that slice—the path stdin/stdout take through the PTY queues between gterm and dps. It is sanitized: it shows how data is supposed to flow so you can reason about it. It is not a claim that ownership is already clean in every repo corner (see §1a), and it is not “here is the entire operating system.”
flowchart TB
subgraph hw["Hardware"]
KB["Keyboard / mouse"]
GPU["Display"]
end
subgraph kernel["Kernel"]
SCHED["Scheduler / tasks"]
SYSCALL["Syscalls"]
PTY["PTY pair\n(src/fs/pty.rs + VFS)"]
VFS["VFS / fds"]
end
subgraph comp["Compositor / WM"]
DG["deskgui — WM v1, focus, dock"]
end
subgraph apps["Userland"]
GT["gterm\n(apps/termd)"]
DP["dps\n(apps/dps)"]
end
KB --> DG
DG -->|"IPC: keys"| GT
GT -->|"write master"| PTY
PTY -->|"read slave"| DP
DP -->|"write slave"| PTY
PTY -->|"read master"| GT
GT -->|"present RGBA"| DG
DG --> GPU
SYSCALL --> PTY
SYSCALL --> VFS
VFS --> PTY
Text version (same story):
[Keyboard] → deskgui (WM) → gterm (key IPC)
↑ │
│ PTY master fd
│ ↕
│ [Kernel PTY: two queues]
│ ↕
│ PTY slave = dps stdin/stdout
│ │
└──── framebuffer ← gterm draws scrollback + tail
PTY object = kernel: one pair (master + slave), byte queues. Not a separate “PTY daemon.”
gterm = one process: owns the master end, opens WM surface, spawns dps with slave bound to stdio (
sys_openpty→sys_pty_rebind_stdon the child path inapps/termd/src/main.rs).dps = normal process: fd 0 / 1 are the slave; it does not know about gterm’s framebuffer.
1a. Reality check — what you actually have right now¶
The pretty diagram is aspirational structure. The codebase is still a spaghetti-adjacent stack: responsibilities leak, contracts are thin, and several layers do “someone else’s job.”
Tension |
Honest description |
|---|---|
PTY “split” |
The pair is one kernel object, but behavior is split: kernel moves bytes; gterm owns master lifecycle + read loop + line splitting; dps owns slave stdio + readline. There is no single userland “PTY service”—you have to hold all three in your head. |
gterm doing shell-ish work |
Scancode → bytes, |
dps doing terminal-ish work |
Per-char echo, backspace rubout, line assembly until |
Compositor in the middle |
Every keystroke and every frame goes through deskgui (focus, WM v1, blit). Nothing is a straight shot from hardware to shell; the desktop is always in the loop. |
Weak contracts |
Most behavior was nailed down after bugs (burst |
Bottom line: §1 is the map you steer toward. §2 is the target ownership we want. The gap between those and “how thick main.rs is in each app” is the real engineering debt—not shame, just truth.
1b. Client RGBA memory (gterm / WM v1)¶
The ping-pong compositor-facing buffer is not on the brk heap with alloc. p1_alloc_framebuffer (SDK/lib/cstd/src/p1_framebuffer.rs, re-exported as p1window::p1_alloc_framebuffer) maps a page-aligned two-half RGBA slab with sys_mmap_anon (syscall 9 into the per-task mmap_current range). SysAllocator in apps/termd/src/main.rs still uses brk only for Window, String, and other small heap objects—so large surfaces do not compete with break growth (see foundation-debt.md §5.3). Oversized ClientBounds vs the initial cap still clamp until a remap path exists.
2. Who owns what (short)¶
Owner |
Responsibility |
|---|---|
Kernel PTY |
Pair of fds, buffering, opaque bytes in both directions. No line editing, no ANSI, no UTF-8 merging across arbitrary chunk boundaries—that’s policy for userland. |
dps |
Shell: prompt, readline, builtins, echo of typed characters to stdout, command execution, what bytes it writes to stdout (plain text today; |
gterm |
Terminal viewer: WM window, read PTY master (see non-blocking |
Rule of thumb: no VT emulator in gterm — but yes display hygiene on the bytes we paint. Full “terminal emulation” (colors in the framebuffer, curses layout) either stays out of the stream, lives in dps / a future libline, or requires a deliberate new layer.
Deliberate split: “What the session means” (dps) vs “how we sanitize and paint lines” (gterm) vs “how fds are plumbed” (kernel PTY).
3. Terminal data flow¶
Keystroke (intended path)¶
Hardware delivers scancode → deskgui routes to focused client (gterm).
gterm
handle_key_press: maps scancode → byte(s) (e.g.\n, printable char). Window close isCtrl+Shift+Q(not bare Escape / raw scancode0x01— avoids VM/driver confusion with Tab). Ctrl+C:sys_signal_foreground(2)when the shell has a foreground child; otherwise0x03to the PTY for dps readline. Extended prefix: deskgui sends(raw & 0x7F), so0xE0arrives as0x60; gterm treats both as extended, then maps arrows to CSI (ESC [ A–D) and extended Backspace-style codes toDEL(0x7F). Policy + layout roadmap:keyboard-input-and-layout.md(newdocs/canonical).gterm
pty_master_write:sys_writeto PTY master fd.Kernel copies master → slave input queue.
dps
sys_readon stdin (fd 0) reads those bytes; readline echoes locally where designed, builds a line until\n, then runs the command.
Nothing in this path touches the compositor except getting the key event into gterm.
One line of shell output (intended path)¶
dps runs
syscall(1, 1, …)(stdout) → slave write.Kernel copies slave → master output queue.
gterm
poll_dps_output:sys_read_with_flagson PTY master withREAD_FLAG_NONBLOCK_PTY, passes each chunk toappend_pty_master_bytes(buffer until\n, strip trailing\r, push one scroll row).gterm
render_terminal: every completed scroll line is stored already folded (fold_pty_bytes_for_display: CSI / OSC / SS3, BS/DEL, TAB→space). The incomplete tail is folded each frame from rawPTY_LINE_TAILso the live prompt line matches what dps echoed. Then blit into the framebuffer;present_wm_v1sends width/height plus source stride and buffer byte length so the compositor does not mis-read padded RGBA rows (seewm-v1-platform-roadmap.md§6, 6b).
What “broken” usually meant (historically)¶
ANSI on stdout with no parser in gterm → escape bytes show as gaps + literal
[32metc. Fix: dps sends plain text for normal output/prompt, or later a shared VT layer—not strip hacks in the viewer.Burst
\nor multi-byte reads → needed correct stdin consumption in dps and one row per\nin gterm scrollback (see code comments indps/termd).
4. Shell platform roadmap (where this stack fits)¶
The byte path in §3 is only one slice. Phase 1 adds TIOCSWINSZ / TIOCGWINSZ, sys_signal_foreground on Ctrl+C, and compositor WIRE_WM_EVENT_CLIENT_BOUNDS (37) so gterm can refresh PTY winsize after WM resize. Remaining gaps (line discipline, full job control, TERM, arrows/history) are in system-architecture.md §7 (system-architecture.md).
Policy reminder: until a deliberate VT layer exists, gterm must not grow cursor / region / SGR bitmap behavior (§2). Escape bytes on the wire are still expected from real tools; fold_pty_bytes_for_display strips or skips them for safe painting, not to emulate xterm. Roadmap items that need real ANSI semantics must land in dps / libline / kernel TTY — or explicitly extend gterm with a spec, not ad hoc.
4b. Multiple dock terminals vs compositor (WM v1)¶
Each gterm is a separate PID with its own PTY + dps child; they do not share RAM by design. If closing one terminal killed or corrupted another, that was never “PTY coupling” — it was compositor + kernel page-table lifecycle (shared PML4[192] cleanup, present slot sizing, hit-test order). Those fixes live outside this file’s byte spine but directly affect multi-terminal sessions.
Read before debugging “close order” again: foundation-debt.md §3.5–§5.2 (§5.2 = COM1 serial signature when closing the front terminal kills the next blit), wm-v1-platform-roadmap.md §6, system-architecture.md §3.1–§3.2, and the documentation-map.md index.
5. Pointers in the tree¶
Piece |
Where |
|---|---|
PTY buffers / pairing |
|
|
|
gterm lifecycle + master I/O + mmap RGBA |
|
dps shell I/O |
|
Dock launches gterm |
|
Multi-window WM / present teardown |
|
Keyboard policy + layout roadmap |
|
If this doc drifts from code, trust the repo and update this file in one pass.