kitty: add kitty terminal graphics protocol support

Adds kitty.c/kitty.h implementing the kitty graphics protocol behind
KITTY_GRAPHICS_PATCH, enabled in patches.h.

Supports transmission (direct, file, temp file, POSIX shared memory),
the RGB/RGBA/PNG formats with a self-contained PNG decoder, zlib
compression, chunked transfers, placements with source rects, scaling,
cell offsets, z-index ordering with alpha blending via XRender,
relative and Unicode-placeholder virtual placements, the full set of
delete selectors, queries, image numbers and animation (frame
transfer, composition and playback).
This commit is contained in:
a 2026-09-06 20:41:24 -05:00
parent e8e4ba8995
commit cb1f41c9ae
No known key found for this signature in database
GPG Key ID: 2F22877AA4DFDADB
10 changed files with 2791 additions and 5 deletions

View File

@ -4,7 +4,7 @@
include config.mk
SRC = st.c x.c $(LIGATURES_C) $(SIXEL_C)
SRC = st.c x.c $(LIGATURES_C) $(SIXEL_C) $(KITTY_C)
OBJ = $(SRC:.c=.o)
all: st
@ -18,8 +18,8 @@ patches.h:
.c.o:
$(CC) $(STCFLAGS) -c $<
st.o: config.h st.h win.h
x.o: arg.h config.h st.h win.h $(LIGATURES_H)
st.o: config.h st.h win.h $(KITTY_H)
x.o: arg.h config.h st.h win.h $(LIGATURES_H) $(KITTY_H)
$(OBJ): config.h config.mk patches.h
@ -32,7 +32,7 @@ clean:
dist: clean
mkdir -p st-$(VERSION)
cp -R FAQ LEGACY TODO LICENSE Makefile README config.mk\
config.def.h st.info st.1 arg.h st.h win.h $(LIGATURES_H) $(SRC)\
config.def.h st.info st.1 arg.h st.h win.h $(LIGATURES_H) $(KITTY_H) $(SRC)\
st-$(VERSION)
tar -cf - st-$(VERSION) | gzip > st-$(VERSION).tar.gz
rm -rf st-$(VERSION)

View File

@ -30,6 +30,11 @@ XRENDER = `$(PKG_CONFIG) --libs xrender`
SIXEL_C = sixel.c sixel_hls.c
SIXEL_LIBS = `$(PKG_CONFIG) --libs imlib2`
# Uncomment this for the kitty graphics patch / KITTY_GRAPHICS_PATCH
KITTY_C = kitty.c
KITTY_H = kitty.h
KITTY_LIBS = `$(PKG_CONFIG) --libs zlib xrender`
# Uncomment for the netwmicon patch / NETWMICON_PATCH
#NETWMICON_LIBS = `$(PKG_CONFIG) --libs gdlib`
@ -38,7 +43,7 @@ INCS = -I$(X11INC) \
`$(PKG_CONFIG) --cflags fontconfig` \
`$(PKG_CONFIG) --cflags freetype2` \
$(LIGATURES_INC)
LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft ${SIXEL_LIBS} ${XRENDER} ${XCURSOR}\
LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft ${SIXEL_LIBS} ${KITTY_LIBS} ${XRENDER} ${XCURSOR}\
`$(PKG_CONFIG) --libs fontconfig` \
`$(PKG_CONFIG) --libs freetype2` \
$(LIGATURES_LIBS) \

2566
kitty.c Normal file

File diff suppressed because it is too large Load Diff

91
kitty.h Normal file
View File

@ -0,0 +1,91 @@
/* kitty.h - kitty terminal graphics protocol for st
*
* See https://sw.kovidgoyal.net/kitty/graphics-protocol/
*/
#ifndef KITTY_H
#define KITTY_H
#if KITTY_GRAPHICS_PATCH
#include <stdint.h>
/* the Unicode placeholder character */
#define KITTY_PLACEHOLDER 0x10EEEE
/* placeholder glyph bits packed into Glyph.kitty */
#define KPH_HAS_ROW (1u << 0)
#define KPH_HAS_COL (1u << 1)
#define KPH_HAS_MSB (1u << 2)
#define KPH_ROW_SHIFT 8
#define KPH_COL_SHIFT 16
#define KPH_MSB_SHIFT 24
/* limits */
#define KITTY_MAX_DIM 16384
#define KITTY_STORAGE_QUOTA (320u * 1024u * 1024u)
#define KITTY_MAX_REL_DEPTH 8
/* a decoded RGBA image (or animation frame) */
typedef struct KittyFrame {
struct KittyFrame *next;
unsigned char *pixels; /* RGBA, width*height*4, may be NULL for lazy frames */
int width, height;
int gap; /* ms until next frame; <0 == gapless */
int transient;
} KittyFrame;
typedef struct KittyImage {
struct KittyImage *next, *prev;
uint32_t id; /* image id (i key) */
uint32_t number; /* image number (I key) */
int width, height; /* dimensions of the root frame */
unsigned char *pixels; /* root frame RGBA data */
size_t datasize;
KittyFrame *frames; /* animation frames, frame 1 == root (pixels) */
int nframes;
int current_frame; /* 1-based */
int anim_state; /* 0 stopped, 2 loading, 3 running */
int loops; /* remaining loops, -1 infinite */
int transient;
uint64_t last_frame_time;/* ms of last frame advance */
unsigned refs; /* number of placements */
} KittyImage;
typedef struct KittyPlacement {
struct KittyPlacement *next, *prev;
KittyImage *image;
uint32_t id; /* placement id (p key) */
int virt; /* U=1 virtual placement (Unicode placeholder) */
/* source rect in image pixels */
int sx, sy, sw, sh;
/* screen position */
int x, y; /* cell coordinates; y is in scrollback space */
int xoff, yoff; /* pixel offset within the first cell */
int cols, rows; /* size on screen in cells */
int z;
int alt; /* belongs to the alternate screen */
/* relative placement */
uint32_t parent_img, parent_plc;
int hoff, voff;
/* scaled pixmap cache */
void *pixmap;
int pm_w, pm_h; /* pixel size the pixmap was rendered at */
int pm_frame; /* frame the pixmap was rendered from */
} KittyPlacement;
/* entry points used by st.c / x.c */
void kitty_apc(const char *buf, size_t len);
void kitty_draw(void);
void kitty_scroll(int top, int bot, int n, int save);
void kitty_deleteplacements(int alt);
void kitty_clearregion(int x1, int y1, int x2, int y2);
void kitty_reset(void);
void kitty_altscreen(int alt);
void kitty_resize(void);
void kitty_trimhistory(int histsize);
int kitty_animating(double *timeout_ms);
int kitty_is_diacritic(Rune u);
int kitty_absorb_diacritic(Glyph *gp, Rune u);
#endif // KITTY_GRAPHICS_PATCH
#endif // KITTY_H

View File

@ -9,6 +9,9 @@ tloaddefscreen(int clear, int loadcursor)
#if SIXEL_PATCH
tdeleteimages();
#endif // SIXEL_PATCH
#if KITTY_GRAPHICS_PATCH
kitty_deleteplacements(1);
#endif // KITTY_GRAPHICS_PATCH
}
col = term.col, row = term.row;
tswapscreen();
@ -37,6 +40,9 @@ tloadaltscreen(int clear, int savecursor)
#if SIXEL_PATCH
tdeleteimages();
#endif // SIXEL_PATCH
#if KITTY_GRAPHICS_PATCH
kitty_deleteplacements(1);
#endif // KITTY_GRAPHICS_PATCH
}
}
@ -59,6 +65,9 @@ tclearglyph(Glyph *gp, int usecurattr)
}
gp->mode = ATTR_NULL;
gp->u = ' ';
#if KITTY_GRAPHICS_PATCH
gp->kitty = 0;
#endif // KITTY_GRAPHICS_PATCH
}
#if SIXEL_PATCH
@ -268,6 +277,9 @@ rscrolldown(int n)
#if SIXEL_PATCH
scroll_images(n - term.scr);
#endif // SIXEL_PATCH
#if KITTY_GRAPHICS_PATCH
kitty_trimhistory(n - term.scr);
#endif // KITTY_GRAPHICS_PATCH
term.scr = 0;
if (sel.ob.x != -1 && !sel.alt)
selmove(-i);
@ -340,6 +352,9 @@ tresizealt(int col, int row)
#if SIXEL_PATCH
scroll_images(-i);
#endif // SIXEL_PATCH
#if KITTY_GRAPHICS_PATCH
kitty_trimhistory(-i);
#endif // KITTY_GRAPHICS_PATCH
term.c.y = row - 1;
}
for (i += row; i < term.row; i++)
@ -412,6 +427,9 @@ kscrolldown(const Arg* a)
#if SIXEL_PATCH
scroll_images(-1*n);
#endif // SIXEL_PATCH
#if KITTY_GRAPHICS_PATCH
kitty_trimhistory(-1*n);
#endif // KITTY_GRAPHICS_PATCH
#if OPENURLONCLICK_PATCH
if (n > 0)
@ -444,6 +462,9 @@ kscrollup(const Arg* a)
#if SIXEL_PATCH
scroll_images(n);
#endif // SIXEL_PATCH
#if KITTY_GRAPHICS_PATCH
kitty_trimhistory(n);
#endif // KITTY_GRAPHICS_PATCH
#if OPENURLONCLICK_PATCH
if (n > 0)
@ -501,6 +522,10 @@ tscrollup(int top, int bot, int n, int mode)
term.line[i+n] = temp;
}
#if KITTY_GRAPHICS_PATCH
kitty_scroll(top, bot, n, savehist);
#endif // KITTY_GRAPHICS_PATCH
#if SIXEL_PATCH
if (alt || !savehist) {
/* move images, if they are inside the scrolling region */
@ -571,6 +596,10 @@ tscrolldown(int top, int n)
term.line[i-n] = temp;
}
#if KITTY_GRAPHICS_PATCH
kitty_scroll(top, bot, -n, 0);
#endif // KITTY_GRAPHICS_PATCH
#if SIXEL_PATCH
/* move images, if they are inside the scrolling region */
for (im = term.images; im; im = next) {

View File

@ -343,6 +343,20 @@
*/
#define SINGLE_DRAWABLE_BUFFER_PATCH 0
/* This patch adds support for the kitty terminal graphics protocol, which lets
* clients display raster images in the terminal. It supports the RGB, RGBA and
* PNG formats, zlib compression, direct/file/temp-file/shared-memory
* transmission, placements (including relative and Unicode-placeholder based
* virtual placements), z-index ordering with alpha blending, image deletion,
* queries and animations.
*
* Note that you need to uncomment the corresponding lines in config.mk when
* including this patch (it needs zlib and Xrender).
*
* https://sw.kovidgoyal.net/kitty/graphics-protocol/
*/
#define KITTY_GRAPHICS_PATCH 0
/* This patch adds SIXEL graphics support for st.
* Note that patch/sixel.c/sixel_hls.c come from mintty, licensed under GPL.
* Known issues:

View File

@ -343,6 +343,20 @@
*/
#define SINGLE_DRAWABLE_BUFFER_PATCH 0
/* This patch adds support for the kitty terminal graphics protocol, which lets
* clients display raster images in the terminal. It supports the RGB, RGBA and
* PNG formats, zlib compression, direct/file/temp-file/shared-memory
* transmission, placements (including relative and Unicode-placeholder based
* virtual placements), z-index ordering with alpha blending, image deletion,
* queries and animations.
*
* Note that you need to uncomment the corresponding lines in config.mk when
* including this patch (it needs zlib and Xrender).
*
* https://sw.kovidgoyal.net/kitty/graphics-protocol/
*/
#define KITTY_GRAPHICS_PATCH 1
/* This patch adds SIXEL graphics support for st.
* Note that patch/sixel.c/sixel_hls.c come from mintty, licensed under GPL.
* Known issues:

42
st.c
View File

@ -30,6 +30,10 @@
#include "sixel.h"
#endif // SIXEL_PATCH
#if KITTY_GRAPHICS_PATCH
#include "kitty.h"
#endif // KITTY_GRAPHICS_PATCH
#if defined(__linux)
#include <pty.h>
#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
@ -1227,6 +1231,9 @@ treset(void)
#if SIXEL_PATCH
tdeleteimages();
#endif // SIXEL_PATCH
#if KITTY_GRAPHICS_PATCH
kitty_deleteplacements(i);
#endif // KITTY_GRAPHICS_PATCH
tswapscreen();
}
#if REFLOW_PATCH
@ -1595,6 +1602,14 @@ tsetchar(Rune u, const Glyph *attr, int x, int y)
if (isboxdraw(u))
term.line[y][x].mode |= ATTR_BOXDRAW;
#endif // BOXDRAW_PATCH
#if KITTY_GRAPHICS_PATCH
term.line[y][x].kitty = 0;
if (u == KITTY_PLACEHOLDER)
term.line[y][x].mode |= ATTR_KITTYPH;
else
term.line[y][x].mode &= ~ATTR_KITTYPH;
#endif // KITTY_GRAPHICS_PATCH
}
#if !REFLOW_PATCH
@ -2219,6 +2234,10 @@ csihandle(void)
#endif // REFLOW_PATCH
break;
case 2: /* screen */
#if KITTY_GRAPHICS_PATCH
/* the clear screen escape also clears all images */
kitty_deleteplacements(IS_SET(MODE_ALTSCREEN));
#endif // KITTY_GRAPHICS_PATCH
#if REFLOW_PATCH
if (IS_SET(MODE_ALTSCREEN)) {
tclearregion(0, 0, term.col-1, term.row-1, 1);
@ -2603,6 +2622,16 @@ strhandle(void)
#endif // SIXEL_PATCH
term.esc &= ~(ESC_STR_END|ESC_STR);
#if KITTY_GRAPHICS_PATCH
/* handled before strparse(), which destroys the ';' separators */
if (strescseq.type == '_') {
strescseq.buf[strescseq.len] = '\0';
kitty_apc(strescseq.buf, strescseq.len);
return;
}
#endif // KITTY_GRAPHICS_PATCH
strparse();
par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
@ -3381,6 +3410,19 @@ check_control_code:
return;
}
#if KITTY_GRAPHICS_PATCH
/* absorb row/column diacritics into the preceding placeholder cell */
if (width == 0 && kitty_is_diacritic(u)) {
int px = term.c.x - ((term.c.state & CURSOR_WRAPNEXT) ? 0 : 1);
if (px >= 0 && px < term.col &&
(term.line[term.c.y][px].mode & ATTR_KITTYPH) &&
kitty_absorb_diacritic(&term.line[term.c.y][px], u)) {
term.dirty[term.c.y] = 1;
return;
}
}
#endif // KITTY_GRAPHICS_PATCH
#if REFLOW_PATCH
/* selected() takes relative coordinates */
if (selected(term.c.x + term.scr, term.c.y + term.scr))

6
st.h
View File

@ -69,6 +69,9 @@ enum glyph_attribute {
#if KEYBOARDSELECT_PATCH && REFLOW_PATCH
ATTR_HIGHLIGHT = 1 << 17,
#endif // KEYBOARDSELECT_PATCH
#if KITTY_GRAPHICS_PATCH
ATTR_KITTYPH = 1 << 18,
#endif // KITTY_GRAPHICS_PATCH
ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,
};
@ -144,6 +147,9 @@ typedef struct {
int ustyle; /* underline style */
int ucolor[3]; /* underline color */
#endif // UNDERCURL_PATCH
#if KITTY_GRAPHICS_PATCH
uint32_t kitty; /* kitty Unicode placeholder row/column/msb */
#endif // KITTY_GRAPHICS_PATCH
} Glyph;
typedef Glyph *Line;

19
x.c
View File

@ -60,6 +60,10 @@ static void zoomreset(const Arg *);
#include "patch/st_include.h"
#include "patch/x_include.h"
#if KITTY_GRAPHICS_PATCH
#include "kitty.h"
#endif // KITTY_GRAPHICS_PATCH
/* config.h for applying patches and the configuration. */
#include "config.h"
@ -332,6 +336,10 @@ zoomabs(const Arg *arg)
}
#endif // SIXEL_PATCH
#if KITTY_GRAPHICS_PATCH
kitty_resize();
#endif // KITTY_GRAPHICS_PATCH
cresize(0, 0);
redraw();
xhints();
@ -3265,6 +3273,10 @@ xfinishdraw(void)
}
#endif // SIXEL_PATCH
#if KITTY_GRAPHICS_PATCH
kitty_draw();
#endif // KITTY_GRAPHICS_PATCH
#if !SINGLE_DRAWABLE_BUFFER_PATCH
XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, win.h, 0, 0);
#endif // SINGLE_DRAWABLE_BUFFER_PATCH
@ -3770,6 +3782,13 @@ run(void)
/* idle detected or maxlatency exhausted -> draw */
timeout = -1;
#if KITTY_GRAPHICS_PATCH
{
double animto = -1;
if (kitty_animating(&animto) && animto >= 0)
timeout = animto;
}
#endif // KITTY_GRAPHICS_PATCH
#if BLINKING_CURSOR_PATCH
if (blinktimeout && (cursorblinks || tattrset(ATTR_BLINK)))
#else