This is a technical deep-dive into how interactive programs like `less` and `fzf` reclaim keyboard input when their stdin is piped—a common pattern in Unix pipelines. The core mechanism: downstream processes drain the pipe with `read_to_string()`, then open `/dev/tty` directly to access the controlling terminal independently of their inherited file descriptors. The author demonstrates this by building a pipeline where each stage is the same binary, showing how a process detects its position (via `is_terminal()` checks), coordinates with earlier stages (pipe EOF acts as the handoff), and competes fairly for input (terminal queue, not broadcast). The post also covers why races don't happen, how background jobs block, and a macOS caveat where `/dev/tty` alone isn't enough—you may need the concrete device like `/dev/ttys003` for kqueue-based polling. Practical for anyone building shell tools or debugging why piped input breaks interactivity.
I'll enumerate the options the source actually discusses for how piped processes can access keyboard input:
**Explicit in the source:**
1. **Open `/dev/tty` directly** — the main technique shown. Creates a new file descriptor pointing to the controlling terminal, letting the process read keystrokes independently of fd 0.
2. **Resolve the concrete device** (e.g., `/dev/ttys003` on macOS) — the practical caveat. Necessary when `/dev/tty` alone fails because polling implementations treat the alias differently than the actual device.
**Implied but not fully explored:**
3. The source doesn't say what `fzf` does specifically when placed mid-pipeline with both stdin and stdout piped (it poses this as an open question at the end). The author knows it works but leaves the mechanism unspecified.
4. **Not reopening stdin at all** — the source notes that terminal input is "a queue, not a broadcast," so technically a downstream process could simply wait and compete for input if both processes tried. But this isn't presented as a workable option, since processes are blocked on their pipes anyway.
The source is clear that `/dev/tty` is the standard pattern, with the macOS device resolution as the one known gotcha. It doesn't enumerate alternative approaches (like signal-based coordination or shared file descriptors) because those aren't how the ecosystem actually works.