Kitty Graphics Protocol

Programs use the Kitty graphics protocol to show real images in a terminal: file previews, plots, diagrams, editor thumbnails. The program sends the image data in an escape sequence and tells the terminal where to draw it.

Nash supports the protocol. Since 0.8.1 it follows the protocol's placement rules closely, so images stack, scroll, erase, and delete the way the specification describes. They behave like text, not like stickers on top of it.

Before you start

  • Install Nash 0.8.1 or newer. The behavior on this page, including Unicode placeholders and capability replies, arrived in 0.8.1.
  • There is nothing to set up. Kitty graphics is on by default. To turn it off, see "Turn the protocol off" below.
  • You rarely write these sequences yourself. The program that shows the image does. You only need this page to understand what you see, or to build a tool.

For nash imgcat and sixel images, see Other Image Protocols. If an image does not appear, start with Kitty Graphics Troubleshooting.

How images behave

Placements

A placement is one display of an image at one position. A program can place the same image many times.

  • A placement without a placement id (p=) adds another placement of the image. A placement with an id replaces the placement that has the same image id and placement id.
  • Overlapping placements stack by z-index (z=), then by image id.
  • Sending new data for an image id replaces the image and removes the placements of the old image.
  • After a placement, the cursor moves to the cell after the last column of the image, on its last row. C=1 keeps the cursor where it was.
  • The primary screen and the alternate screen each have their own images and placements, as in kitty. A full-screen program does not remove or change the images in your shell's screen.

Size and position

  • Without c= and r=, Nash draws the image at its pixel size, starting at the cursor cell.
  • c= and r= set the number of cells. If you set only one of them, Nash computes the other so the image keeps its aspect ratio. If you set both, the image stretches to fill them.
  • X= and Y= move the image by a number of pixels inside the first cell. The offset is part of the area that c= and r= describe. Nash limits it to one pixel less than the cell size, and each slice of the image stays inside its own cell, so a neighboring cell background cannot cover a piece of it.
  • x=, y=, w=, and h= select a rectangle of the source image. Nash refuses an empty rectangle.

Layers

z-indexWhere Nash draws the image
0 and aboveAbove the text
Below 0, down to -1073741824Beneath the text, on top of cell backgrounds
Below -1073741824Beneath the backgrounds of cells too, as in kitty

Images beneath the text stay below underlines, the selection, and the cursor. A cell with a non-default background color hides an image whose z-index is below -1073741824.

Text, scrolling, and erasing

  • Images move with their lines. They follow scrolling, scroll regions, and the scrollback. A program such as Neovim can scroll a window with a scroll region and its images follow.
  • Erasing text with EL, ECH, or ED (below or above the cursor) leaves images whole. It does not cut holes into them.
  • Clearing the screen with ED 2 removes the placements on the screen. Erasing the scrollback with ED 3 removes the placements that lived in it.
  • Images keep their position and stacking order when wide-character edits, horizontal scroll margins, or a resize rewrap a line.
  • An image under double-width text, such as CJK characters or emoji, does not turn the text into spaces or repeat a slice of the image. Double-width text written over an image leaves the image whole.

Deleting

Deleting one placement does not remove the other placements of its image. The full list of delete modes is in the reference below. A lowercase delete removes placements only. An uppercase delete also frees the image data, but only when no placement still shows the image.

Unicode placeholders

Some programs cannot position an image with cursor movement alone. An editor that draws windows and floating panels needs the image to move, scroll, clip, and disappear together with the text around it. Unicode placeholders solve that.

  1. The program creates a virtual placement with a=p,U=1. It records how an image maps onto a grid of cells, but draws nothing.
  2. The program prints the placeholder character U+10EEEE in each cell of the grid. The foreground color carries the image id. The underline color carries an optional placement id. Combining characters after the placeholder carry the row, the column, and the high byte of the image id.
  3. Nash draws the matching tile of the image in each placeholder cell.

Because the image position is ordinary text, the image follows every edit of that text. Nash also handles the details that make this robust:

  • The order does not matter. The program can send the placement before the text, or the text before the placement, and it can resize the placement later without printing the text again.
  • If a placeholder leaves out its row or column characters, Nash fills them in from the placeholder on its left, as kitty does.
  • The image fits inside the grid with its aspect ratio kept and is centered. If the grid size is 0, it follows from the size of the image.
  • Nash draws the image beneath the placeholder cell, so the cursor stays visible on top.
  • The tiles also work in the scrollback, and each screen has its own virtual placements.
  • A placeholder without a placement id shows the newest virtual placement of its image.

The snacks.nvim Neovim plugin uses this mechanism to show images inside windows and floats when the terminal supports it. It is also how programs place images inside tmux.

Capability queries and replies

A program that wants to know whether your terminal shows images sends a query (a=q). Nash answers without loading or storing anything:

program sends: ESC _ G i=31,s=1,v=1,a=q,t=d,f=24;AAAA ESC \ Nash replies: ESC _ G i=31;OK ESC \
  • Both ST (ESC \) and BEL end a query. The reply always ends with ST.
  • Nash does not read the payload, open a file, decode pixels, assign an image id, or replace an existing image. An upload in chunks that is in progress is not interrupted by a query.
  • A malformed or unsupported query gets EINVAL instead of silence, so the program does not wait for a timeout. Examples are an unknown f=, t=, or o= value, a non-numeric number, both i= and I=, a size of 0, or m=1.
  • Some programs probe with a real upload instead of a=q. Nash acknowledges a successful upload with OK for an image id (i=) and for an image number (I=), unless the program asked for quiet replies with q=1 or q=2.
  • If Kitty graphics is turned off, Nash does not answer at all.

Reference: what Nash accepts

Actions

ActionKeySupport
Transmita=tSupported
Transmit and displaya=TSupported
Placea=pSupported, including U=1 for a virtual placement
Deletea=dSupported, except animation frames
Querya=qSupported
Animation framea=fAccepted
Frame compositiona=cAccepted
Animation controla=aNot supported

Transmission

FeatureKeysNotes
Direct datat=d (default)Base64 in the sequence. Send large images in chunks with m=1.
Filet=fThe payload is the base64 path. S= and O= set size and offset.
Temporary filet=tNash deletes the file after it reads it, but only inside /tmp/, /var/tmp/, /dev/shm/, or $TMPDIR.
Shared memoryt=sOn Unix and Windows. The payload is the base64 object name.
RGB, RGBA, PNGf=24, f=32, f=100f=32 is the default. RGB and RGBA need s= and v=.
Compressiono=zzlib deflate
Image id, numberi=, I=Use one of them, not both in one request.

Placement keys

x=, y=, w=, h= (source rectangle), X=, Y= (pixel offset), c=, r= (cells), C= (cursor), p= (placement id), z= (z-index), and U= (virtual placement) are supported.

Delete modes

Each mode has a lowercase form that removes placements and an uppercase form that also frees the image data when nothing refers to it.

ModeDeletes
d=a, d=AAll placements on the visible screen. This is the default.
d=i, d=IPlacements of image i=, or only placement p= when you give it.
d=n, d=NPlacements of the newest image with number I=.
d=c, d=CPlacements at the cursor position.
d=p, d=PPlacements at cell x=, y=. Numbering starts at 1.
d=q, d=QPlacements at cell x=, y= with z-index z=.
d=x, d=XPlacements that cross column x=, including the scrollback.
d=y, d=YPlacements that cross row y=.
d=z, d=ZPlacements with z-index z=.
d=r, d=RImages with an id from x= to y=.
d=f, d=FAnimation frames. Not supported. Nash ignores the request.

Deletes by position and by z-index do not touch virtual placements, because a virtual placement has no position. Delete by image id to remove one.

Differences from kitty and limits

  • A failed command gets no error reply. Nash writes the reason to its log and carries on. The exceptions are a bad query (EINVAL) and some frame commands (ENOENT). A program that waits for a reply to a failed upload waits for its own timeout.
  • No animation control. A program can send frames and compose them, but it cannot start, stop, or loop an animation with a=a.
  • Large images are refused. Nash ignores an image whose decoded RGBA data is larger than 100 MB, which is about 25 million pixels.
  • Stored image data has a fixed budget of 320 MiB. When the stored data goes over it, Nash frees the images that no placement or placeholder uses. You cannot change this limit.
  • Nash reads files and shared memory itself. The path or object name in a t=f, t=t, or t=s upload must be visible to the Nash process that reads the sequence. For a program on a remote machine, use direct transmission.
  • A pane with no pixel size refuses images. Nash cannot compute how many cells an image covers without the pixel size of a cell.

Try it

Replace the path with a PNG file on your machine:

img="$HOME/Pictures/example.png" printf '\033_Ga=T,f=100,t=f,c=20,q=2;%s\033\\' \ "$(printf '%s' "$img" | base64 | tr -d '\n')" printf '\n'

The command sends the file path (t=f), asks Nash to display it (a=T) 20 cells wide (c=20), and keeps the aspect ratio because it sets no r=. The q=2 key stops Nash from sending a reply into your shell's input. Then print more lines so the image scrolls, resize the window, or run clear to see it removed. The image follows the rules above.

tmux, SSH, and the multiplexer

  • tmux does not understand the protocol itself. Image sequences must pass through tmux to reach Nash. Programs that run in tmux place their images with Unicode placeholders, described above.
  • SSH. The sequences travel through the connection like other output. Files and shared memory do not. See the note on file transmission above.
  • Multiplexer sessions. The multiplexer server keeps your panes and sends the images to the window. Nash 0.8.1 changed the multiplexer protocol to version 48. A server that speaks an older protocol version must be restarted. nash mux status shows the version of the server and the version of your build. See Multiplexer before you restart, because a restart closes every pane the server owns.

Turn the protocol off

Kitty graphics has no key in nash.toml. To turn it off, create custom.lua in your Nash configuration directory (~/.config/nash by default):

return { enable_kitty_graphics = false }

Reload the configuration. Nash then ignores every Kitty graphics sequence, including capability queries, so programs fall back to other output.

Related