[RFC] Content Rendering Protocol with terminal-retained source
- Dominant language
- Python
- Stars
- 34.9k
- Forks
- 1.5k
- Avg merge
- 8h 28m
- Merged PRs (30d)
- 43
Description
## Problem
The [Kitty Graphics Protocol (KGP)](https://sw.kovidgoyal.net/kitty/graphics-protocol/) lets applications transmit rendered images. A running local application can rerender after a font change or use a shared local tool, but applications should not each carry a rendering engine. Only the terminal can rerender scrollback after the producing command exits, return the original source on copy, or serve a remote application without a renderer on the remote host.
Applications could instead send a content kind and source to a renderer in the terminal:
- The same protocol works across terminal hosts.
- Applications can query support for each kind.
- One render request returns natural or constrained size in rows and columns.
- Virtual placements provide that size before the client writes placeholders.
- Retained source enables rerendering on zoom and copying as text.
- Renderers can be internal or supplied by locally configured shared objects.
- No content kind is mandatory; an empty registry returns `ENOTSUP`.
## Working examples
A kitty prototype implements the protocol and shared-object loader with independent providers, here showing `math`, `smiles`, `abc` and `barcode/`. Patches are ready to submit once the design is agreed.
| Molecules (`smiles`) | Music (`abc`) | Barcodes (`barcode/qr`) |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| |
|
|
The first image is a kitty screenshot; the others show provider output. The molecule is caffeine: `Cn1c(=O)c2c(ncn2C)n(C)c1=O`. The same protocol and providers also work in Ghostty.
## Wire protocol
The protocol uses KGP-style controls and placement under a separate `fffc` APC prefix. It can become a KGP extension if preferred.
```text
_fffc;;\
```
`fffc` is literal ASCII. Controls are comma-separated `key=value` pairs. Decoded source bytes pass unchanged to the renderer. Queries may omit the payload separator. Unknown single-letter keys are ignored.
| Key | Meaning |
| ------------ | ------------------------------------------------------------------------------------------------------------------- |
| `a=q`, `a=d` | Query support; render and place. Default: `d`. |
| `t` | Case-sensitive kind: 1–64 ASCII letters, digits, `-_.+/`. Omit on a query to ask whether any renderer is available. |
| `R` | Unsigned 32-bit request token, echoed in replies including errors. |
| `c`, `r` | Exact columns and rows; zero means unspecified. |
| `M`, `N` | Maximum columns and rows; zero means unspecified. |
| `F=1` | Preserve natural scale; shrink only. Default: scale to fill an exact box. |
| `i`, `p` | Image and placement IDs. Missing or zero `i` requests allocation. |
| `U=1` | Create a virtual placement for KGP Unicode placeholders. Default: place at cursor. |
| `z` | Placement z-index. |
| `C=1` | Leave the cursor unchanged. |
| `q=0/1/2` | Normal replies / errors only / no replies. |
Each render returns geometry; no separate measurement request is needed. Width shrinks to `M`; height exceeding `N` fails. Exact sizes exceeding bounds fail. Bounded canvases round to whole cells, preserving aspect ratio and padding as needed.
Replies echo the action and token. Render replies add image ID, pixel dimensions (`w/h`), cell dimensions (`c/r`) and baseline offset from the top in pixels (`b`). For `\boxed{E=mc^2}` at 9 × 18 pixel cells:
```text
C → T _fffc;a=q,t=math,R=1\
T → C _fffc;a=q,R=1,t=math;OK\
C → T _fffc;a=d,t=math,R=2,M=80,N=12,F=1,U=1;XGJveGVke0U9bWNeMn0=\
T → C _fffc;a=d,R=2,i=1,w=72,h=36,c=8,r=2,b=24;OK\
C → T _fffc;a=q,t=unknown,R=3\
T → C _fffc;a=q,R=3,t=unknown;ENOTSUP: unsupported kind\
```
With `U=1`, the client then writes KGP's U+10EEEE placeholders. Errors include `ENOTSUP`, `EINVAL`, `EFBIG` and `ENOMEM`. For detection, send a support query followed by DA1; a supporting host answers the query first. Clients can fall back to text or raster images.
Kitty retains source, kind, colors, sizing policy and the final cell box. Font changes rerender into that box at the new pixel size. Copying substitutes source or provider-supplied alternate text for placeholder cells.
## Renderers
Kitty can use internal renderers or locally configured shared objects. Multiple objects can be loaded, each declaring one or more kinds. The last successful declaration of a kind wins; load failures are logged and skipped. Terminal output selects a registered kind, never a library path.
The prototype uses this C interface (size and version fields omitted):
```c
typedef struct {
const uint8_t *kind;
size_t kind_len;
int32_t (*render)(void *context, const TcAllocator *, const TcRequest *, TcResult *);
} TcRenderer;
typedef struct {
int32_t (*init)(const TcAllocator *, void **context, const TcRenderer **renderers, size_t *count);
void (*destroy)(void *context);
} TcProvider;
const TcProvider *terminal_content_provider(uint32_t abi_version);
```
`TcRequest` carries source, cell metrics, colors, dimensions and a pixel-byte limit. `TcResult` returns RGBA8 pixels, geometry and optional copy text. `TcAllocator` supplies host allocation callbacks; successful output belongs to the host. Shared objects can serve multiple hosts with a compatible ABI.
One context per library serves all windows and tabs. Kitty calls renderers synchronously from the main loop, serializing calls per library. Provider changes require restart. Providers own their dependencies, so kitty has no build dependency on them.
Providers run with kitty's privileges and must bound work and terminate without executing client-supplied programs. Native callbacks cannot be safely preempted. Prototype limits: 128 KiB source, 64 MiB pixels per image, 10,000 pixels per dimension.
## Alternatives considered
We considered a live filter that replaces LaTeX in terminal output with KGP images. A simple filter loses interactivity in TUIs such as AI agents; preserving it requires tracking cursor movement and redraws. External rendering tools add process and image-transfer overhead, require installation and tool-specific integration or wrappers, and lack common capability discovery. Registered renderers let applications query support for each kind.
## Questions
I'd like to upstream whichever pieces you're open to.
1. Is the protocol acceptable in principle, and should it use a separate APC prefix or new KGP actions?
2. Is a user-configured shared-object loader acceptable for kitty?
Contributor guide
Research direction
No files, tests, or code entry points are named; start by reviewing the proposed wire protocol, renderer ABI, and shared-object loader described here. Done means the protocol and loader design receive maintainer agreement so the referenced kitty prototype patches can be submitted.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c, python
- Domain
- cli
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100