termio: add zmx backend for persistent terminal sessions

MD
7mo ago
+1112-48

Summary

This PR adds a new zmx termio backend to Ghostty and wires it through config + embedded APIs so surfaces can attach to persistent zmx sessions instead of always spawning a direct PTY subprocess.

Why

cmux persistence work (manaflow-ai/cmux#143, manaflow-ai/cmux#480) needs terminal processes to outlive the app. The existing exec backend ties process lifetime to the Ghostty surface, so restart always loses shell state. zmx provides a daemon-owned PTY/session model that can survive app restarts and reconnect cleanly.

What Changed

1) New zmx backend in termio

  • Added src/termio/Zmx.zig.
  • Added zmx variant to src/termio/backend.zig unions/dispatch.
  • Exported backend in src/termio.zig.

Backend behavior:

  • Connects to zmx over Unix domain socket.
  • Supports session creation (zmx run <session>) when configured.
  • Uses framed IPC for input/output/resize.
  • Sends Init + immediate Resize on attach.
  • On unexpected socket disconnect, emits .child_exited metadata so surface UX remains coherent.
  • On planned shutdown, sends Detach and exits cleanly without false error reporting.

2) Surface/backend selection and fallback

  • Updated src/Surface.zig to choose zmx backend when zmx-session is set.
  • If zmx binary is unavailable, falls back to exec with warning (no crash/no hard failure).

3) Config + embedded API surface

  • Added config keys in src/config/Config.zig:
    • zmx-session
    • zmx-create
  • Added per-surface embedded options in src/apprt/embedded.zig and synced C header:
    • zmx_session
    • zmx_create
    • zmx_mode
  • Updated include/ghostty.h to keep C API struct in sync.

zmx_mode is used for split/tab inheritance in embedded mode: new surfaces created from a zmx-backed surface stay in zmx mode, each with a fresh session.

4) Build compatibility in this branch

  • Added a local uucode fallback vendoring path and compatibility wiring (deps-uucode, unicode build updates) to keep fork builds stable in environments where fetching unicode dependency sources is constrained.

Design Rationale

  • Backend extension point: implemented as a first-class termio backend variant, not a special-case branch inside exec, so behavior stays isolated and maintainable.
  • Per-surface control: zmx session is configured at surface creation time (embedded options), which maps naturally to split/tab semantics.
  • Graceful degradation: missing zmx does not break terminal creation; it downgrades to exec.
  • Socket-readiness gating: session creation is treated as successful when the socket is connectable, avoiding false negatives from launcher exit-code quirks.

Validation

  • Zmx.zig includes targeted tests for IPC framing/buffer behavior and disconnect metadata.
  • End-to-end integration validated through cmux Debug builds and persistence workflows using this Ghostty branch.

Related

Summary by CodeRabbit

  • New Features

    • Added a zmx terminal backend and new options to specify/create/use zmx sessions (zmx-session, zmx-create, zmx-mode). Surfaces propagate zmx settings to newly opened surfaces.
  • Behavior

    • zmx backend is selected when configured; abnormal-exit messages include session info. Surfaces now emit a persistent “child disconnected” notification for backend disconnects.
  • Tests

    • Added unit tests for zmx IPC framing, config parsing, socket/path handling, and disconnect metadata.