Compare commits

...

8 Commits

Author SHA1 Message Date
Nicholas Marriott
1a9ac07961 Add test images. 2026-03-13 16:48:39 +00:00
Thomas Adam
274ebb2613 Feedback from nicm 2026-03-12 08:20:05 +00:00
Thomas Adam
9c3ec2b8eb Refactor kitty images to use unified image cache API
The original kitty implementation used a passthrough approach where images
were forwarded directly to the outer terminal without being stored in tmux's
image cache.

This refactors kitty images to work like sixel images.
2026-03-05 17:21:40 +00:00
Thomas Adam
aefdb34846 rename: ENABLE_SIXEL to ENABLE_SIXEL_IMAGES 2026-03-05 17:21:18 +00:00
Thomas Adam
5a233e26ec rename ENABLE_KITTY to ENABLE_KITTY_IMAGES 2026-03-05 17:21:18 +00:00
Thomas Adam
1634db8589 configure: deprecate enable-sixel
This moves --enable-sixel to --enable-sixel-images
2026-03-05 17:21:18 +00:00
Thomas Adam
3b7e9fb175 format: add image_support
This adds a new image_support format which returns:

   "sixel" -- if sixel is compiled in
   "kitty" -- if kitty is compiled in
   "kitty,sixel" -- if compiled with both kitty and sixel
2026-03-05 17:21:18 +00:00
Thomas Adam
cf6cbe430c Add support for the kitty graphics protocol
Kitty-capable terminals (kitty, ghostty, and others) can display
inline images via APC escape sequences.

Kitty's image support uses a passthrough model: APC sequences from
programs running inside panes are relayed verbatim to the outer
terminal with cursor positioning adjusted for the pane offset.

The outer terminal handles all image rendering and therefore itself must
be kitty-aware.
2026-03-05 17:21:18 +00:00
17 changed files with 997 additions and 87 deletions

View File

@@ -232,8 +232,18 @@ nodist_tmux_SOURCES += compat/utf8proc.c
endif
# Enable sixel support.
if ENABLE_SIXEL
if ENABLE_SIXEL_IMAGES
dist_tmux_SOURCES += image.c image-sixel.c
else
# If not sixel, still need image.c for kitty.
if ENABLE_KITTY_IMAGES
dist_tmux_SOURCES += image.c
endif
endif
# Enable kitty graphics protocol support.
if ENABLE_KITTY_IMAGES
dist_tmux_SOURCES += image-kitty.c
endif
if NEED_FUZZING

View File

@@ -463,13 +463,32 @@ fi
# Enable sixel support.
AC_ARG_ENABLE(
sixel,
AS_HELP_STRING(--enable-sixel, enable sixel images)
sixel-images,
AS_HELP_STRING(--enable-sixel-images, enable sixel images)
)
if test "x$enable_sixel" = xyes; then
AC_DEFINE(ENABLE_SIXEL)
AC_ARG_ENABLE(
sixel,
AS_HELP_STRING(--enable-sixel support),
[
enable_sixel_images="$enableval"
]
)
if test "x$enable_sixel_images" = xyes; then
AC_DEFINE(ENABLE_SIXEL_IMAGES)
fi
AM_CONDITIONAL(ENABLE_SIXEL, [test "x$enable_sixel" = xyes])
AM_CONDITIONAL(ENABLE_SIXEL_IMAGES, [test "x$enable_sixel_images" = xyes])
# Enable kitty graphics protocol support.
AC_ARG_ENABLE(
kitty-images,
AS_HELP_STRING(--enable-kitty-images, enable kitty terminal graphics)
)
if test "x$enable_kitty_images" = xyes; then
AC_DEFINE(ENABLE_KITTY_IMAGES)
fi
AM_CONDITIONAL(ENABLE_KITTY_IMAGES, [test "x$enable_kitty_images" = xyes])
# Check for b64_ntop. If we have b64_ntop, we assume b64_pton as well.
AC_MSG_CHECKING(for b64_ntop)

View File

@@ -2562,12 +2562,16 @@ format_cb_version(__unused struct format_tree *ft)
/* Callback for sixel_support. */
static void *
format_cb_sixel_support(__unused struct format_tree *ft)
format_cb_image_support(__unused struct format_tree *ft)
{
#ifdef ENABLE_SIXEL
return (xstrdup("1"));
#if defined(ENABLE_SIXEL_IMAGES) && defined(ENABLE_KITTY_IMAGES)
return (xstrdup("kitty,sixel"));
#elif defined(ENABLE_SIXEL_IMAGES)
return (xstrdup("sixel"));
#elif defined(ENABLE_KITTY_IMAGES)
return (xstrdup("kitty"));
#else
return (xstrdup("0"));
return (NULL);
#endif
}
@@ -3190,6 +3194,9 @@ static const struct format_table_entry format_table[] = {
{ "host_short", FORMAT_TABLE_STRING,
format_cb_host_short
},
{ "image_support", FORMAT_TABLE_STRING,
format_cb_image_support
},
{ "insert_flag", FORMAT_TABLE_STRING,
format_cb_insert_flag
},
@@ -3466,9 +3473,6 @@ static const struct format_table_entry format_table[] = {
{ "session_windows", FORMAT_TABLE_STRING,
format_cb_session_windows
},
{ "sixel_support", FORMAT_TABLE_STRING,
format_cb_sixel_support
},
{ "socket_path", FORMAT_TABLE_STRING,
format_cb_socket_path
},

325
image-kitty.c Normal file
View File

@@ -0,0 +1,325 @@
/* $OpenBSD$ */
/*
* Copyright (c) 2026 Thomas Adam <thomas@xteddy.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include "tmux.h"
/*
* kitty_image stores the raw decoded pixel data and metadata from a kitty
* graphics protocol APC sequence. It is used to re-emit the sequence to the
* outer terminal on redraw.
*/
struct kitty_image {
/* Control-data fields parsed from the APC sequence. */
char action; /* a=: 'T'=transmit+display, 't', 'p', 'd' */
u_int format; /* f=: 32=RGBA, 24=RGB, 100=PNG */
char medium; /* t=: 'd'=direct, 'f'=file, 't'=tmp, 's'=shm */
u_int pixel_w; /* s=: source image pixel width */
u_int pixel_h; /* v=: source image pixel height */
u_int cols; /* c=: display columns (0=auto) */
u_int rows; /* r=: display rows (0=auto) */
u_int image_id; /* i=: image id (0=unassigned) */
u_int image_num; /* I=: image number */
u_int placement_id; /* p=: placement id */
u_int more; /* m=: 1=more chunks coming, 0=last */
u_int quiet; /* q=: suppress responses */
int z_index; /* z=: z-index */
char compression; /* o=: 'z'=zlib, 0=none */
char delete_what; /* d=: delete target (used with a=d) */
/* Cell size at the time of parsing (from the owning window). */
u_int xpixel;
u_int ypixel;
/* Original base64-encoded payload (concatenated across all chunks). */
char *encoded;
size_t encodedlen;
char *ctrl;
size_t ctrllen;
};
/*
* Parse control-data key=value pairs from a kitty APC sequence.
* Format: key=value,key=value,...
*/
static int
kitty_parse_control(const char *ctrl, size_t ctrllen, struct kitty_image *ki)
{
const char *p = ctrl, *end = ctrl + ctrllen, *errstr;
char key[4], val[32];
size_t klen, vlen;
while (p < end) {
klen = 0;
while (p < end && *p != '=' && klen < sizeof(key) - 1)
key[klen++] = *p++;
key[klen] = '\0';
if (p >= end || *p != '=')
return (-1);
p++;
vlen = 0;
while (p < end && *p != ',' && vlen < sizeof(val) - 1)
val[vlen++] = *p++;
val[vlen] = '\0';
if (p < end && *p == ',')
p++;
if (klen != 1)
continue;
switch (key[0]) {
case 'a':
ki->action = val[0];
break;
case 'f':
ki->format = strtonum(val, 0, UINT_MAX, &errstr);
if (errstr != NULL)
return (-1);
break;
case 't':
ki->medium = val[0];
break;
case 's':
ki->pixel_w = strtonum(val, 0, UINT_MAX, &errstr);
if (errstr != NULL)
return (-1);
break;
case 'v':
ki->pixel_h = strtonum(val, 0, UINT_MAX, &errstr);
if (errstr != NULL)
return (-1);
break;
case 'c':
ki->cols = strtonum(val, 0, UINT_MAX, &errstr);
if (errstr != NULL)
return (-1);
break;
case 'r':
ki->rows = strtonum(val, 0, UINT_MAX, &errstr);
if (errstr != NULL)
return (-1);
break;
case 'i':
ki->image_id = strtonum(val, 0, UINT_MAX, &errstr);
if (errstr != NULL)
return (-1);
break;
case 'I':
ki->image_num = strtonum(val, 0, UINT_MAX, &errstr);
if (errstr != NULL)
return (-1);
break;
case 'p':
ki->placement_id = strtonum(val, 0, UINT_MAX, &errstr);
if (errstr != NULL)
return (-1);
break;
case 'm':
ki->more = strtonum(val, 0, UINT_MAX, &errstr);
if (errstr != NULL)
return (-1);
break;
case 'q':
ki->quiet = strtonum(val, 0, UINT_MAX, &errstr);
if (errstr != NULL)
return (-1);
break;
case 'z':
ki->z_index = strtonum(val, INT_MIN, INT_MAX, &errstr);
if (errstr != NULL)
return (-1);
break;
case 'o':
ki->compression = val[0];
break;
case 'd':
ki->delete_what = val[0];
break;
}
}
return (0);
}
/*
* Parse a kitty APC body (after the leading 'G').
* Stores the original control string and base64 payload verbatim for
* pass-through re-emission to the outer terminal.
*/
struct kitty_image *
kitty_parse(const u_char *buf, size_t len, u_int xpixel, u_int ypixel)
{
struct kitty_image *ki;
const u_char *semi;
const char *ctrl;
size_t ctrllen, paylen;
if (len == 0)
return (NULL);
semi = memchr(buf, ';', len);
if (semi != NULL) {
ctrl = (const char *)buf;
ctrllen = semi - buf;
paylen = len - ctrllen - 1;
} else {
ctrl = (const char *)buf;
ctrllen = len;
paylen = 0;
}
ki = xcalloc(1, sizeof *ki);
ki->xpixel = xpixel;
ki->ypixel = ypixel;
ki->action = 'T';
ki->format = 32;
ki->medium = 'd';
if (kitty_parse_control(ctrl, ctrllen, ki) != 0) {
free(ki);
return (NULL);
}
if (paylen > 0) {
ki->encoded = xmalloc(paylen + 1);
memcpy(ki->encoded, semi + 1, paylen);
ki->encoded[paylen] = '\0';
ki->encodedlen = paylen;
}
ki->ctrl = xmalloc(ctrllen + 1);
memcpy(ki->ctrl, ctrl, ctrllen);
ki->ctrl[ctrllen] = '\0';
ki->ctrllen = ctrllen;
return (ki);
}
void
kitty_free(struct kitty_image *ki)
{
if (ki == NULL)
return;
free(ki->encoded);
free(ki->ctrl);
free(ki);
}
/*
* Get the size in cells of a kitty image. If cols/rows are 0 (auto),
* calculate from pixel dimensions. Returns size via sx/sy pointers.
*/
void
kitty_size_in_cells(struct kitty_image *ki, u_int *sx, u_int *sy)
{
*sx = ki->cols;
*sy = ki->rows;
/*
* If cols/rows are 0, they mean "auto" - calculate from
* pixel dimensions.
*/
if (*sx == 0 && ki->pixel_w > 0 && ki->xpixel > 0) {
*sx = (ki->pixel_w + ki->xpixel - 1) / ki->xpixel;
}
if (*sy == 0 && ki->pixel_h > 0 && ki->ypixel > 0) {
*sy = (ki->pixel_h + ki->ypixel - 1) / ki->ypixel;
}
/* If still 0, use a reasonable default */
if (*sx == 0)
*sx = 10;
if (*sy == 0)
*sy = 10;
}
char
kitty_get_action(struct kitty_image *ki)
{
return (ki->action);
}
u_int
kitty_get_image_id(struct kitty_image *ki)
{
return (ki->image_id);
}
u_int
kitty_get_rows(struct kitty_image *ki)
{
return (ki->rows);
}
/*
* Serialize a kitty_image back into an APC escape sequence for transmission
* to the terminal. This recreates the original command that was parsed.
*/
char *
kitty_print(struct kitty_image *ki, size_t *outlen)
{
char *out;
size_t total, pos;
if (ki == NULL || ki->ctrl == NULL)
return (NULL);
/* Calculate total length: ESC _ G + ctrl + ; + encoded + ESC \ */
total = 3 + ki->ctrllen; /* \033_G + ctrl */
if (ki->encoded != NULL && ki->encodedlen > 0) {
total += 1 + ki->encodedlen; /* ; + encoded */
}
total += 2; /* \033\\ */
out = xmalloc(total + 1);
*outlen = total;
/* Build the sequence */
pos = 0;
memcpy(out + pos, "\033_G", 3);
pos += 3;
memcpy(out + pos, ki->ctrl, ki->ctrllen);
pos += ki->ctrllen;
if (ki->encoded != NULL && ki->encodedlen > 0) {
out[pos++] = ';';
memcpy(out + pos, ki->encoded, ki->encodedlen);
pos += ki->encodedlen;
}
memcpy(out + pos, "\033\\", 2);
pos += 2;
out[pos] = '\0';
return (out);
}
char *
kitty_delete_all(size_t *outlen)
{
char *out;
out = xstrdup("\033_Ga=d,d=a\033\\");
*outlen = strlen(out);
return (out);
}

112
image.c
View File

@@ -61,7 +61,22 @@ image_free(struct image *im)
all_images_count--;
TAILQ_REMOVE(&s->images, im, entry);
sixel_free(im->data);
switch (im->type) {
#ifdef ENABLE_SIXEL_IMAGES
case IMAGE_SIXEL:
sixel_free(im->data.sixel);
break;
#endif
#ifdef ENABLE_KITTY_IMAGES
case IMAGE_KITTY:
kitty_free(im->data.kitty);
break;
#endif
default:
break;
}
free(im->fallback);
free(im);
}
@@ -81,13 +96,30 @@ image_free_all(struct screen *s)
/* Create text placeholder for an image. */
static void
image_fallback(char **ret, u_int sx, u_int sy)
image_fallback(char **ret, enum image_type type, u_int sx, u_int sy)
{
char *buf, *label;
u_int py, size, lsize;
const char *type_name;
switch (type) {
#ifdef ENABLE_SIXEL_IMAGES
case IMAGE_SIXEL:
type_name = "SIXEL";
break;
#endif
#ifdef ENABLE_KITTY_IMAGES
case IMAGE_KITTY:
type_name = "KITTY";
break;
#endif
default:
type_name = "UNKNOWN";
break;
}
/* Allocate first line. */
lsize = xasprintf(&label, "SIXEL IMAGE (%ux%u)\r\n", sx, sy) + 1;
lsize = xasprintf(&label, "%s IMAGE (%ux%u)\r\n", type_name, sx, sy) + 1;
if (sx < lsize - 3)
size = lsize - 1;
else
@@ -122,19 +154,36 @@ image_fallback(char **ret, u_int sx, u_int sy)
}
struct image*
image_store(struct screen *s, struct sixel_image *si)
image_store(struct screen *s, enum image_type type, void *data)
{
struct image *im;
im = xcalloc(1, sizeof *im);
im->type = type;
im->s = s;
im->data = si;
im->px = s->cx;
im->py = s->cy;
sixel_size_in_cells(si, &im->sx, &im->sy);
image_fallback(&im->fallback, im->sx, im->sy);
switch (type) {
#ifdef ENABLE_SIXEL_IMAGES
case IMAGE_SIXEL:
im->data.sixel = data;
sixel_size_in_cells(im->data.sixel, &im->sx, &im->sy);
break;
#endif
#ifdef ENABLE_KITTY_IMAGES
case IMAGE_KITTY:
im->data.kitty = data;
kitty_size_in_cells(im->data.kitty, &im->sx, &im->sy);
break;
#endif
default:
break;
}
image_fallback(&im->fallback, type, im->sx, im->sy);
image_log(im, __func__, NULL);
TAILQ_INSERT_TAIL(&s->images, im, entry);
@@ -188,8 +237,10 @@ image_scroll_up(struct screen *s, u_int lines)
{
struct image *im, *im1;
int redraw = 0;
u_int sx, sy;
#ifdef ENABLE_SIXEL_IMAGES
struct sixel_image *new;
u_int sx, sy;
#endif
TAILQ_FOREACH_SAFE(im, &s->images, entry, im1) {
if (im->py >= lines) {
@@ -204,20 +255,43 @@ image_scroll_up(struct screen *s, u_int lines)
redraw = 1;
continue;
}
sx = im->sx;
sy = (im->py + im->sy) - lines;
image_log(im, __func__, "3, lines=%u, sy=%u", lines, sy);
new = sixel_scale(im->data, 0, 0, 0, im->sy - sy, sx, sy, 1);
sixel_free(im->data);
im->data = new;
/* Image is partially scrolled off - need to crop it */
switch (im->type) {
#ifdef ENABLE_SIXEL_IMAGES
case IMAGE_SIXEL:
sx = im->sx;
sy = (im->py + im->sy) - lines;
image_log(im, __func__, "sixel, lines=%u, sy=%u",
lines, sy);
im->py = 0;
sixel_size_in_cells(im->data, &im->sx, &im->sy);
new = sixel_scale(im->data.sixel, 0, 0, 0, im->sy - sy,
sx, sy, 1);
sixel_free(im->data.sixel);
im->data.sixel = new;
free(im->fallback);
image_fallback(&im->fallback, im->sx, im->sy);
redraw = 1;
im->py = 0;
sixel_size_in_cells(im->data.sixel, &im->sx, &im->sy);
free(im->fallback);
image_fallback(&im->fallback, im->type, im->sx, im->sy);
redraw = 1;
break;
#endif
#ifdef ENABLE_KITTY_IMAGES
case IMAGE_KITTY:
/*
* For kitty images, we can't rescale - the terminal
* owns the placement. Just adjust position and let
* the terminal handle clipping.
*/
im->py = 0;
redraw = 1;
break;
#endif
default:
break;
}
}
return (redraw);
}

101
input.c
View File

@@ -145,6 +145,7 @@ struct input_ctx {
*/
struct evbuffer *since_ground;
struct event ground_timer;
};
/* Helper functions. */
@@ -180,6 +181,7 @@ static void input_ground(struct input_ctx *);
static void input_enter_dcs(struct input_ctx *);
static void input_enter_osc(struct input_ctx *);
static void input_exit_osc(struct input_ctx *);
static int input_da1_has_sixel(struct input_ctx *);
static void input_enter_apc(struct input_ctx *);
static void input_exit_apc(struct input_ctx *);
static void input_enter_rename(struct input_ctx *);
@@ -1381,6 +1383,10 @@ input_esc_dispatch(struct input_ctx *ictx)
input_reset_cell(ictx);
screen_write_reset(sctx);
screen_write_fullredraw(sctx);
#ifdef ENABLE_KITTY_IMAGES
if (ictx->wp != NULL)
tty_kitty_delete_all_pane(ictx->wp);
#endif
break;
case INPUT_ESC_IND:
screen_write_linefeed(sctx, 0, ictx->cell.cell.bg);
@@ -1556,11 +1562,10 @@ input_csi_dispatch(struct input_ctx *ictx)
case -1:
break;
case 0:
#ifdef ENABLE_SIXEL
input_reply(ictx, 1, "\033[?1;2;4c");
#else
input_reply(ictx, 1, "\033[?1;2c");
#endif
if (input_da1_has_sixel(ictx))
input_reply(ictx, 1, "\033[?1;2;4c");
else
input_reply(ictx, 1, "\033[?1;2c");
break;
default:
log_debug("%s: unknown '%c'", __func__, ictx->ch);
@@ -2030,7 +2035,7 @@ input_csi_dispatch_sm_private(struct input_ctx *ictx)
static void
input_csi_dispatch_sm_graphics(__unused struct input_ctx *ictx)
{
#ifdef ENABLE_SIXEL
#ifdef ENABLE_SIXEL_IMAGES
int n, m, o;
if (ictx->param_list_len > 3)
@@ -2555,7 +2560,7 @@ input_dcs_dispatch(struct input_ctx *ictx)
const char prefix[] = "tmux;";
const u_int prefixlen = (sizeof prefix) - 1;
long long allow_passthrough = 0;
#ifdef ENABLE_SIXEL
#ifdef ENABLE_SIXEL_IMAGES
struct window *w;
struct sixel_image *si;
int p2;
@@ -2570,7 +2575,7 @@ input_dcs_dispatch(struct input_ctx *ictx)
return (0);
}
#ifdef ENABLE_SIXEL
#ifdef ENABLE_SIXEL_IMAGES
w = wp->window;
if (buf[0] == 'q' && ictx->interm_len == 0) {
if (input_split(ictx) != 0)
@@ -2716,6 +2721,79 @@ input_enter_apc(struct input_ctx *ictx)
ictx->flags &= ~INPUT_LAST;
}
/*
* Check if any client viewing this pane has an outer terminal that supports
* sixel, so we can report it in the DA1 response.
*/
static int
input_da1_has_sixel(__unused struct input_ctx *ictx)
{
#ifdef ENABLE_SIXEL_IMAGES
struct window_pane *wp = ictx->wp;
struct client *c;
if (wp == NULL)
return (0);
TAILQ_FOREACH(c, &clients, entry) {
if (c->session == NULL)
continue;
if (c->session->curw->window != wp->window)
continue;
if (c->tty.term->flags & TERM_SIXEL)
return (1);
}
#endif
return (0);
}
#ifdef ENABLE_KITTY_IMAGES
/* Handle a kitty graphics APC sequence. */
static void
input_apc_kitty_image(struct input_ctx *ictx)
{
struct screen_write_ctx *sctx = &ictx->ctx;
struct window_pane *wp = ictx->wp;
struct window *w;
struct kitty_image *ki;
if (wp == NULL)
return;
w = wp->window;
ki = kitty_parse(ictx->input_buf + 1, ictx->input_len - 1,
w->xpixel, w->ypixel);
if (ki == NULL)
return;
/* Handle query commands. */
if (kitty_get_action(ki) == 'q') {
if (kitty_get_image_id(ki) != 0)
input_reply(ictx, 0, "\033_Gi=%u;OK\033\\",
kitty_get_image_id(ki));
else
input_reply(ictx, 0, "\033_Ga=q;OK\033\\");
kitty_free(ki);
return;
}
/* Store image placements and trigger a redraw. */
if (kitty_get_action(ki) == 'T' || kitty_get_action(ki) == 't' ||
kitty_get_action(ki) == 'p') {
screen_write_kittyimage(sctx, ki);
} else {
/* For other actions (delete, etc.), pass through. */
char *apc;
size_t apclen;
apclen = xasprintf(&apc, "\033_%s\033\\", ictx->input_buf);
tty_kitty_passthrough(wp, apc, apclen, sctx->s->cx,
sctx->s->cy);
free(apc);
kitty_free(ki);
}
}
#endif
/* APC terminator (ST) received. */
static void
input_exit_apc(struct input_ctx *ictx)
@@ -2727,6 +2805,13 @@ input_exit_apc(struct input_ctx *ictx)
return;
log_debug("%s: \"%s\"", __func__, ictx->input_buf);
#ifdef ENABLE_KITTY_IMAGES
if (ictx->input_len >= 1 && ictx->input_buf[0] == 'G') {
input_apc_kitty_image(ictx);
return;
}
#endif
if (wp != NULL &&
options_get_number(wp->options, "allow-set-title") &&
screen_set_title(sctx->s, ictx->input_buf)) {

View File

@@ -667,6 +667,16 @@ screen_redraw_screen(struct client *c)
}
if (flags & CLIENT_REDRAWWINDOW) {
log_debug("%s: redrawing panes", c->name);
#ifdef ENABLE_KITTY_IMAGES
/*
* Delete all kitty image placements before redrawing panes.
* This must happen unconditionally — even when the new window
* has no images — so that images from the previous window
* (or from a `reset` in the shell) are cleared from the outer
* terminal before new content is drawn over them.
*/
tty_kitty_delete_all(&c->tty);
#endif
screen_redraw_draw_panes(&ctx);
screen_redraw_draw_pane_scrollbars(&ctx);
}
@@ -697,8 +707,12 @@ screen_redraw_pane(struct client *c, struct window_pane *wp,
tty_sync_start(&c->tty);
tty_update_mode(&c->tty, c->tty.mode, NULL);
if (!redraw_scrollbar_only)
if (!redraw_scrollbar_only) {
#ifdef ENABLE_KITTY_IMAGES
tty_kitty_delete_all(&c->tty);
#endif
screen_redraw_draw_pane(&ctx, wp);
}
if (window_pane_show_scrollbar(wp, ctx.pane_scrollbars))
screen_redraw_draw_pane_scrollbar(&ctx, wp);
@@ -1002,7 +1016,7 @@ screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp)
}
}
#ifdef ENABLE_SIXEL
#ifdef ENABLE_SIXEL_IMAGES
tty_draw_images(c, wp, s);
#endif
}

View File

@@ -1072,7 +1072,7 @@ screen_write_alignmenttest(struct screen_write_ctx *ctx)
memcpy(&gc, &grid_default_cell, sizeof gc);
utf8_set(&gc.data, 'E');
#ifdef ENABLE_SIXEL
#ifdef ENABLE_SIXEL_IMAGES
if (image_free_all(s) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#endif
@@ -1111,7 +1111,7 @@ screen_write_insertcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
if (s->cx > screen_size_x(s) - 1)
return;
#ifdef ENABLE_SIXEL
#ifdef ENABLE_SIXEL_IMAGES
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#endif
@@ -1144,7 +1144,7 @@ screen_write_deletecharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
if (s->cx > screen_size_x(s) - 1)
return;
#ifdef ENABLE_SIXEL
#ifdef ENABLE_SIXEL_IMAGES
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#endif
@@ -1177,7 +1177,7 @@ screen_write_clearcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
if (s->cx > screen_size_x(s) - 1)
return;
#ifdef ENABLE_SIXEL
#ifdef ENABLE_SIXEL_IMAGES
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#endif
@@ -1200,14 +1200,14 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
struct grid *gd = s->grid;
struct tty_ctx ttyctx;
#ifdef ENABLE_SIXEL
#ifdef ENABLE_SIXEL_IMAGES
u_int sy = screen_size_y(s);
#endif
if (ny == 0)
ny = 1;
#ifdef ENABLE_SIXEL
#ifdef ENABLE_SIXEL_IMAGES
if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#endif
@@ -1260,7 +1260,7 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
if (ny == 0)
ny = 1;
#ifdef ENABLE_SIXEL
#ifdef ENABLE_SIXEL_IMAGES
if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#endif
@@ -1313,7 +1313,7 @@ screen_write_clearline(struct screen_write_ctx *ctx, u_int bg)
if (gl->cellsize == 0 && COLOUR_DEFAULT(bg))
return;
#ifdef ENABLE_SIXEL
#ifdef ENABLE_SIXEL_IMAGES
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#endif
@@ -1347,7 +1347,7 @@ screen_write_clearendofline(struct screen_write_ctx *ctx, u_int bg)
if (s->cx > sx - 1 || (s->cx >= gl->cellsize && COLOUR_DEFAULT(bg)))
return;
#ifdef ENABLE_SIXEL
#ifdef ENABLE_SIXEL_IMAGES
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#endif
@@ -1374,7 +1374,7 @@ screen_write_clearstartofline(struct screen_write_ctx *ctx, u_int bg)
return;
}
#ifdef ENABLE_SIXEL
#ifdef ENABLE_SIXEL_IMAGES
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#endif
@@ -1422,7 +1422,7 @@ screen_write_reverseindex(struct screen_write_ctx *ctx, u_int bg)
struct tty_ctx ttyctx;
if (s->cy == s->rupper) {
#ifdef ENABLE_SIXEL
#ifdef ENABLE_SIXEL_IMAGES
if (image_free_all(s) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#endif
@@ -1469,7 +1469,7 @@ screen_write_linefeed(struct screen_write_ctx *ctx, int wrapped, u_int bg)
struct screen *s = ctx->s;
struct grid *gd = s->grid;
struct grid_line *gl;
#ifdef ENABLE_SIXEL
#ifdef ENABLE_SIXEL_IMAGES
int redraw = 0;
#endif
u_int rupper = s->rupper, rlower = s->rlower;
@@ -1487,7 +1487,7 @@ screen_write_linefeed(struct screen_write_ctx *ctx, int wrapped, u_int bg)
}
if (s->cy == s->rlower) {
#ifdef ENABLE_SIXEL
#ifdef ENABLE_SIXEL_IMAGES
if (rlower == screen_size_y(s) - 1)
redraw = image_scroll_up(s, 1);
else
@@ -1520,7 +1520,7 @@ screen_write_scrollup(struct screen_write_ctx *ctx, u_int lines, u_int bg)
ctx->bg = bg;
}
#ifdef ENABLE_SIXEL
#ifdef ENABLE_SIXEL_IMAGES
if (image_scroll_up(s, lines) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#endif
@@ -1549,7 +1549,7 @@ screen_write_scrolldown(struct screen_write_ctx *ctx, u_int lines, u_int bg)
else if (lines > s->rlower - s->rupper + 1)
lines = s->rlower - s->rupper + 1;
#ifdef ENABLE_SIXEL
#ifdef ENABLE_SIXEL_IMAGES
if (image_free_all(s) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#endif
@@ -1578,7 +1578,7 @@ screen_write_clearendofscreen(struct screen_write_ctx *ctx, u_int bg)
struct tty_ctx ttyctx;
u_int sx = screen_size_x(s), sy = screen_size_y(s);
#ifdef ENABLE_SIXEL
#ifdef ENABLE_SIXEL_IMAGES
if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#endif
@@ -1612,7 +1612,7 @@ screen_write_clearstartofscreen(struct screen_write_ctx *ctx, u_int bg)
struct tty_ctx ttyctx;
u_int sx = screen_size_x(s);
#ifdef ENABLE_SIXEL
#ifdef ENABLE_SIXEL_IMAGES
if (image_check_line(s, 0, s->cy - 1) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#endif
@@ -1640,7 +1640,7 @@ screen_write_clearscreen(struct screen_write_ctx *ctx, u_int bg)
struct tty_ctx ttyctx;
u_int sx = screen_size_x(s), sy = screen_size_y(s);
#ifdef ENABLE_SIXEL
#ifdef ENABLE_SIXEL_IMAGES
if (image_free_all(s) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#endif
@@ -1957,7 +1957,7 @@ screen_write_collect_end(struct screen_write_ctx *ctx)
}
}
#ifdef ENABLE_SIXEL
#ifdef ENABLE_SIXEL_IMAGES
if (image_check_area(s, s->cx, s->cy, ci->used, 1) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#endif
@@ -2398,7 +2398,7 @@ screen_write_rawstring(struct screen_write_ctx *ctx, u_char *str, u_int len,
tty_write(tty_cmd_rawstring, &ttyctx);
}
#ifdef ENABLE_SIXEL
#ifdef ENABLE_SIXEL_IMAGES
/* Write a SIXEL image. */
void
screen_write_sixelimage(struct screen_write_ctx *ctx, struct sixel_image *si,
@@ -2450,7 +2450,7 @@ screen_write_sixelimage(struct screen_write_ctx *ctx, struct sixel_image *si,
screen_write_collect_flush(ctx, 0, __func__);
screen_write_initctx(ctx, &ttyctx, 0);
ttyctx.ptr = image_store(s, si);
ttyctx.ptr = image_store(s, IMAGE_SIXEL, si);
tty_write(tty_cmd_sixelimage, &ttyctx);
@@ -2458,6 +2458,38 @@ screen_write_sixelimage(struct screen_write_ctx *ctx, struct sixel_image *si,
}
#endif
#ifdef ENABLE_KITTY_IMAGES
void
screen_write_kittyimage(struct screen_write_ctx *ctx, struct kitty_image *ki)
{
struct screen *s = ctx->s;
struct tty_ctx ttyctx;
struct image *im;
if (ki == NULL)
return;
/* Store the image in the cache. */
im = image_store(s, IMAGE_KITTY, ki);
/* Trigger a tty write to send to all terminals. */
if (im != NULL && ctx->wp != NULL) {
screen_write_collect_flush(ctx, 0, __func__);
screen_write_initctx(ctx, &ttyctx, 0);
ttyctx.ptr = im;
ttyctx.arg = ctx->wp;
ttyctx.ocx = s->cx;
ttyctx.ocy = s->cy;
ttyctx.set_client_cb = tty_set_client_cb;
tty_write(tty_cmd_kittyimage, &ttyctx);
}
/* Move cursor past the image. */
if (kitty_get_rows(ki) > 0)
screen_write_cursormove(ctx, 0, s->cy + kitty_get_rows(ki), 0);
}
#endif
/* Turn alternate screen on. */
void
screen_write_alternateon(struct screen_write_ctx *ctx, struct grid_cell *gc,

View File

@@ -89,7 +89,7 @@ screen_init(struct screen *s, u_int sx, u_int sy, u_int hlimit)
s->tabs = NULL;
s->sel = NULL;
#ifdef ENABLE_SIXEL
#ifdef ENABLE_IMAGES
TAILQ_INIT(&s->images);
TAILQ_INIT(&s->saved_images);
#endif
@@ -127,7 +127,7 @@ screen_reinit(struct screen *s)
screen_clear_selection(s);
screen_free_titles(s);
#ifdef ENABLE_SIXEL
#ifdef ENABLE_IMAGES
image_free_all(s);
#endif
@@ -164,7 +164,7 @@ screen_free(struct screen *s)
hyperlinks_free(s->hyperlinks);
screen_free_titles(s);
#ifdef ENABLE_SIXEL
#ifdef ENABLE_IMAGES
image_free_all(s);
#endif
}
@@ -324,7 +324,7 @@ screen_resize_cursor(struct screen *s, u_int sx, u_int sy, int reflow,
if (sy != screen_size_y(s))
screen_resize_y(s, sy, eat_empty, &cy);
#ifdef ENABLE_SIXEL
#ifdef ENABLE_IMAGES
image_free_all(s);
#endif
@@ -649,7 +649,7 @@ screen_alternate_on(struct screen *s, struct grid_cell *gc, int cursor)
}
memcpy(&s->saved_cell, gc, sizeof s->saved_cell);
#ifdef ENABLE_SIXEL
#ifdef ENABLE_IMAGES
TAILQ_CONCAT(&s->saved_images, &s->images, entry);
#endif
@@ -707,7 +707,7 @@ screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor)
grid_destroy(s->saved_grid);
s->saved_grid = NULL;
#ifdef ENABLE_SIXEL
#ifdef ENABLE_IMAGES
image_free_all(s);
TAILQ_CONCAT(&s->images, &s->saved_images, entry);
#endif

2
tmux.1
View File

@@ -6266,6 +6266,7 @@ The following variables are available, where appropriate:
.It Li "hook_window_name" Ta "" Ta "Name of window where hook was run, if any"
.It Li "host" Ta "#H" Ta "Hostname of local host"
.It Li "host_short" Ta "#h" Ta "Hostname of local host (no domain name)"
.It Li "image_support" Ta "" Ta "Returns the string of the support image (sixel, kitty)"
.It Li "insert_flag" Ta "" Ta "Pane insert flag"
.It Li "key_string" Ta "" Ta "String representation of the key binding"
.It Li "key_repeat" Ta "" Ta "1 if key binding is repeatable"
@@ -6380,7 +6381,6 @@ The following variables are available, where appropriate:
.It Li "session_stack" Ta "" Ta "Window indexes in most recent order"
.It Li "session_windows" Ta "" Ta "Number of windows in session"
.It Li "socket_path" Ta "" Ta "Server socket path"
.It Li "sixel_support" Ta "" Ta "1 if server has support for SIXEL"
.It Li "start_time" Ta "" Ta "Server start time"
.It Li "synchronized_output_flag" Ta "" Ta "1 if pane has synchronized output enabled"
.It Li "uid" Ta "" Ta "Server UID"

73
tmux.h
View File

@@ -68,10 +68,19 @@ struct screen_write_cline;
struct screen_write_ctx;
struct session;
#ifdef ENABLE_SIXEL
/* Convenience macro: defined if any image protocol is compiled in. */
#if defined(ENABLE_SIXEL_IMAGES) || defined(ENABLE_KITTY_IMAGES)
#define ENABLE_IMAGES
#endif
#ifdef ENABLE_SIXEL_IMAGES
struct sixel_image;
#endif
#ifdef ENABLE_KITTY_IMAGES
struct kitty_image;
#endif
struct tty_ctx;
struct tty_code;
struct tty_key;
@@ -925,11 +934,25 @@ struct style {
enum style_default_type default_type;
};
#ifdef ENABLE_SIXEL
#ifdef ENABLE_IMAGES
/* Image types. */
enum image_type {
IMAGE_SIXEL,
IMAGE_KITTY
};
/* Image. */
struct image {
enum image_type type;
struct screen *s;
struct sixel_image *data;
union {
#ifdef ENABLE_SIXEL_IMAGES
struct sixel_image *sixel;
#endif
#ifdef ENABLE_KITTY_IMAGES
struct kitty_image *kitty;
#endif
} data;
char *fallback;
u_int px;
@@ -984,7 +1007,7 @@ struct screen {
bitstr_t *tabs;
struct screen_sel *sel;
#ifdef ENABLE_SIXEL
#ifdef ENABLE_IMAGES
struct images images;
struct images saved_images;
#endif
@@ -1566,6 +1589,7 @@ struct tty_term {
#define TERM_RGBCOLOURS 0x10
#define TERM_VT100LIKE 0x20
#define TERM_SIXEL 0x40
#define TERM_KITTY 0x80
int flags;
LIST_ENTRY(tty_term) entry;
@@ -2620,7 +2644,7 @@ struct visible_ranges *tty_check_overlay_range(struct tty *, u_int, u_int,
void tty_draw_line(struct tty *, struct screen *, u_int, u_int, u_int,
u_int, u_int, const struct grid_cell *, struct colour_palette *);
#ifdef ENABLE_SIXEL
#ifdef ENABLE_IMAGES
void tty_draw_images(struct client *, struct window_pane *, struct screen *);
#endif
@@ -2654,10 +2678,22 @@ void tty_cmd_reverseindex(struct tty *, const struct tty_ctx *);
void tty_cmd_setselection(struct tty *, const struct tty_ctx *);
void tty_cmd_rawstring(struct tty *, const struct tty_ctx *);
#ifdef ENABLE_SIXEL
#ifdef ENABLE_IMAGES
int tty_set_client_cb(struct tty_ctx *, struct client *);
#endif
#ifdef ENABLE_SIXEL_IMAGES
void tty_cmd_sixelimage(struct tty *, const struct tty_ctx *);
#endif
#ifdef ENABLE_KITTY_IMAGES
void tty_cmd_kittyimage(struct tty *, const struct tty_ctx *);
void tty_kitty_delete_all(struct tty *);
void tty_kitty_delete_all_pane(struct window_pane *);
void tty_kitty_passthrough(struct window_pane *, const char *, size_t,
u_int, u_int);
#endif
void tty_cmd_syncstart(struct tty *, const struct tty_ctx *);
void tty_default_colours(struct grid_cell *, struct window_pane *);
@@ -3260,10 +3296,15 @@ void screen_write_setselection(struct screen_write_ctx *, const char *,
u_char *, u_int);
void screen_write_rawstring(struct screen_write_ctx *, u_char *, u_int,
int);
#ifdef ENABLE_SIXEL
#ifdef ENABLE_SIXEL_IMAGES
void screen_write_sixelimage(struct screen_write_ctx *,
struct sixel_image *, u_int);
#endif
#ifdef ENABLE_KITTY_IMAGES
void screen_write_kittyimage(struct screen_write_ctx *,
struct kitty_image *);
#endif
void screen_write_alternateon(struct screen_write_ctx *,
struct grid_cell *, int);
void screen_write_alternateoff(struct screen_write_ctx *,
@@ -3727,14 +3768,16 @@ struct window_pane *spawn_pane(struct spawn_context *, char **);
/* regsub.c */
char *regsub(const char *, const char *, const char *, int);
#ifdef ENABLE_SIXEL
#ifdef ENABLE_IMAGES
/* image.c */
int image_free_all(struct screen *);
struct image *image_store(struct screen *, struct sixel_image *);
struct image *image_store(struct screen *, enum image_type, void *);
int image_check_line(struct screen *, u_int, u_int);
int image_check_area(struct screen *, u_int, u_int, u_int, u_int);
int image_scroll_up(struct screen *, u_int);
#endif
#ifdef ENABLE_SIXEL_IMAGES
/* image-sixel.c */
#define SIXEL_COLOUR_REGISTERS 1024
struct sixel_image *sixel_parse(const char *, size_t, u_int, u_int, u_int);
@@ -3748,6 +3791,18 @@ char *sixel_print(struct sixel_image *, struct sixel_image *,
struct screen *sixel_to_screen(struct sixel_image *);
#endif
#ifdef ENABLE_KITTY_IMAGES
/* image-kitty.c */
struct kitty_image *kitty_parse(const u_char *, size_t, u_int, u_int);
void kitty_free(struct kitty_image *);
void kitty_size_in_cells(struct kitty_image *, u_int *, u_int *);
char kitty_get_action(struct kitty_image *);
u_int kitty_get_image_id(struct kitty_image *);
u_int kitty_get_rows(struct kitty_image *);
char *kitty_print(struct kitty_image *, size_t *);
char *kitty_delete_all(size_t *);
#endif
/* server-acl.c */
void server_acl_init(void);
struct server_acl_user *server_acl_user_find(uid_t);

BIN
tools/image.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

1
tools/image.kitty Normal file

File diff suppressed because one or more lines are too long

View File

@@ -357,6 +357,17 @@ static const struct tty_feature tty_feature_sixel = {
TERM_SIXEL
};
/* Terminal has kitty graphics protocol capability. */
static const char *const tty_feature_kitty_capabilities[] = {
"Kty",
NULL
};
static const struct tty_feature tty_feature_kitty = {
"kitty",
tty_feature_kitty_capabilities,
TERM_KITTY
};
/* Available terminal features. */
static const struct tty_feature *const tty_features[] = {
&tty_feature_256,
@@ -368,6 +379,7 @@ static const struct tty_feature *const tty_features[] = {
&tty_feature_extkeys,
&tty_feature_focus,
&tty_feature_ignorefkeys,
&tty_feature_kitty,
&tty_feature_margins,
&tty_feature_mouse,
&tty_feature_osc7,
@@ -500,7 +512,19 @@ tty_default_features(int *feat, const char *name, u_int version)
*/
.features = TTY_FEATURES_BASE_MODERN_XTERM
",ccolour,cstyle,extkeys,focus"
}
},
#ifdef ENABLE_KITTY_IMAGES
{ .name = "kitty",
.features = TTY_FEATURES_BASE_MODERN_XTERM
",ccolour,cstyle,extkeys,focus,sync,hyperlinks"
",kitty"
},
{ .name = "ghostty",
.features = TTY_FEATURES_BASE_MODERN_XTERM
",ccolour,cstyle,extkeys,focus,sync,hyperlinks"
",kitty"
},
#endif
};
u_int i;

View File

@@ -60,6 +60,10 @@ static int tty_keys_device_attributes2(struct tty *, const char *, size_t,
static int tty_keys_extended_device_attributes(struct tty *, const char *,
size_t, size_t *);
static int tty_keys_palette(struct tty *, const char *, size_t, size_t *);
#ifdef ENABLE_KITTY_IMAGES
static int tty_keys_kitty_graphics(struct tty *, const char *, size_t,
size_t *);
#endif
/* A key tree entry. */
struct tty_key {
@@ -759,6 +763,19 @@ tty_keys_next(struct tty *tty)
goto partial_key;
}
#ifdef ENABLE_KITTY_IMAGES
/* Is this a kitty graphics protocol response? ESC _ G ... ESC \ */
switch (tty_keys_kitty_graphics(tty, buf, len, &size)) {
case 0: /* yes */
key = KEYC_UNKNOWN;
goto complete_key;
case -1: /* no, or not valid */
break;
case 1: /* partial */
goto partial_key;
}
#endif
/* Is this a primary device attributes response? */
switch (tty_keys_device_attributes(tty, buf, len, &size)) {
case 0: /* yes */
@@ -1640,6 +1657,14 @@ tty_keys_extended_device_attributes(struct tty *tty, const char *buf,
tty_default_features(features, "mintty", 0);
else if (strncmp(tmp, "foot(", 5) == 0)
tty_default_features(features, "foot", 0);
#ifdef ENABLE_KITTY_IMAGES
else if (strncmp(tmp, "kitty ", 6) == 0 ||
strcmp(tmp, "kitty") == 0)
tty_default_features(features, "kitty", 0);
else if (strncmp(tmp, "ghostty ", 8) == 0 ||
strcmp(tmp, "ghostty") == 0)
tty_default_features(features, "ghostty", 0);
#endif
log_debug("%s: received extended DA %.*s", c->name, (int)*size, buf);
free(c->term_type);
@@ -1795,3 +1820,74 @@ tty_keys_palette(struct tty *tty, const char *buf, size_t len, size_t *size)
return (0);
}
#ifdef ENABLE_KITTY_IMAGES
/*
* Handle kitty graphics protocol response from outer terminal.
* Format: ESC _ G <key=value,...> ; <message> ESC \
* The response to our capability probe (a=q) is:
* ESC _ G i=31;OK ESC \ (supported)
* ESC _ G i=31;ENOSYS:... (not supported)
* Returns 0 for success, -1 for not a kitty response, 1 for partial.
*/
static int
tty_keys_kitty_graphics(struct tty *tty, const char *buf, size_t len,
size_t *size)
{
struct client *c = tty->client;
int *features = &c->term_features;
const char *semi;
size_t i;
char tmp[256];
*size = 0;
/*
* Kitty APC response starts with ESC _ G (3 bytes).
* The 8-bit C1 equivalent 0x9f could also be used but is rare.
*/
if (buf[0] != '\033')
return (-1);
if (len == 1)
return (1);
if (buf[1] != '_')
return (-1);
if (len == 2)
return (1);
if (buf[2] != 'G')
return (-1);
if (len == 3)
return (1);
/* Collect body up to ESC \ (ST). */
for (i = 0; i < sizeof(tmp) - 1; i++) {
if (3 + i + 1 >= len)
return (1); /* partial */
if (buf[3 + i] == '\033' && buf[3 + i + 1] == '\\') {
tmp[i] = '\0';
*size = 3 + i + 2;
break;
}
tmp[i] = buf[3 + i];
}
if (i == sizeof(tmp) - 1)
return (-1); /* too long, not a valid response */
log_debug("%s: kitty graphics response: %s", c->name, tmp);
/*
* Check if the message (after the semicolon) starts with "OK".
* The format is: i=31;OK or i=31;ENOSYS:...
*/
semi = strchr(tmp, ';');
if (semi != NULL && strncmp(semi + 1, "OK", 2) == 0) {
log_debug("%s: kitty graphics supported", c->name);
tty_add_features(features, "kitty", ",");
tty_update_features(tty);
} else {
log_debug("%s: kitty graphics not supported", c->name);
}
return (0);
}
#endif

View File

@@ -461,6 +461,9 @@ tty_term_apply_overrides(struct tty_term *term)
/* Log the SIXEL flag. */
log_debug("SIXEL flag is %d", !!(term->flags & TERM_SIXEL));
/* Log the KITTY flag. */
log_debug("KITTY flag is %d", !!(term->flags & TERM_KITTY));
/* Update the RGB flag if the terminal has RGB colours. */
if (tty_term_has(term, TTYC_SETRGBF) &&
tty_term_has(term, TTYC_SETRGBB))

182
tty.c
View File

@@ -67,7 +67,7 @@ static void tty_emulate_repeat(struct tty *, enum tty_code_code,
static void tty_draw_pane(struct tty *, const struct tty_ctx *, u_int);
static int tty_check_overlay(struct tty *, u_int, u_int);
#ifdef ENABLE_SIXEL
#ifdef ENABLE_IMAGES
static void tty_write_one(void (*)(struct tty *, const struct tty_ctx *),
struct client *, struct tty_ctx *);
#endif
@@ -391,6 +391,23 @@ tty_send_requests(struct tty *tty)
return;
if (tty->term->flags & TERM_VT100LIKE) {
#ifdef ENABLE_KITTY_IMAGES
/*
* Send the kitty graphics capability probe BEFORE the DA1
* request. Per the kitty spec, a supporting terminal sends
* its APC response before processing any subsequent requests.
* So the reply ordering will be:
* 1. ESC _ G i=31;OK ESC \ (kitty response, if supported)
* 2. ESC [ ? ... c (DA1 response)
* 3. ESC [ > ... c (DA2 response)
* 4. ESC P > | ... ESC \ (XDA response)
* which is exactly what our parsers expect.
* Only probe if the kitty feature isn't already enabled.
*/
if (~tty->term->flags & TERM_KITTY)
tty_puts(tty,
"\033_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\033\\");
#endif
if (~tty->flags & TTY_HAVEDA)
tty_puts(tty, "\033[c");
if (~tty->flags & TTY_HAVEDA2)
@@ -1442,9 +1459,9 @@ tty_check_overlay_range(struct tty *tty, u_int px, u_int py, u_int nx)
return (c->overlay_check(c, c->overlay_data, px, py, nx));
}
#ifdef ENABLE_SIXEL
#ifdef ENABLE_IMAGES
/* Update context for client. */
static int
int
tty_set_client_cb(struct tty_ctx *ttyctx, struct client *c)
{
struct window_pane *wp = ttyctx->arg;
@@ -1489,7 +1506,22 @@ tty_draw_images(struct client *c, struct window_pane *wp, struct screen *s)
ttyctx.arg = wp;
ttyctx.set_client_cb = tty_set_client_cb;
ttyctx.allow_invisible_panes = 1;
tty_write_one(tty_cmd_sixelimage, c, &ttyctx);
/* Call the appropriate rendering function based on image type */
switch (im->type) {
#ifdef ENABLE_SIXEL_IMAGES
case IMAGE_SIXEL:
tty_write_one(tty_cmd_sixelimage, c, &ttyctx);
break;
#endif
#ifdef ENABLE_KITTY_IMAGES
case IMAGE_KITTY:
tty_write_one(tty_cmd_kittyimage, c, &ttyctx);
break;
#endif
default:
break;
}
}
}
#endif
@@ -1567,7 +1599,7 @@ tty_write(void (*cmdfn)(struct tty *, const struct tty_ctx *),
}
}
#ifdef ENABLE_SIXEL
#ifdef ENABLE_IMAGES
/* Only write to the incoming tty instead of every client. */
static void
tty_write_one(void (*cmdfn)(struct tty *, const struct tty_ctx *),
@@ -2092,12 +2124,12 @@ tty_cmd_rawstring(struct tty *tty, const struct tty_ctx *ctx)
tty_invalidate(tty);
}
#ifdef ENABLE_SIXEL
#ifdef ENABLE_SIXEL_IMAGES
void
tty_cmd_sixelimage(struct tty *tty, const struct tty_ctx *ctx)
{
struct image *im = ctx->ptr;
struct sixel_image *si = im->data;
struct sixel_image *si = im->data.sixel;
struct sixel_image *new;
char *data;
size_t size;
@@ -2144,6 +2176,142 @@ tty_cmd_sixelimage(struct tty *tty, const struct tty_ctx *ctx)
}
#endif
#ifdef ENABLE_KITTY_IMAGES
static int
tty_has_kitty(struct tty *tty)
{
return (tty->term->flags & TERM_KITTY);
}
void
tty_cmd_kittyimage(struct tty *tty, const struct tty_ctx *ctx)
{
struct image *im = ctx->ptr;
char *data;
size_t size;
u_int cx = ctx->ocx, cy = ctx->ocy;
int fallback = 0;
if (im == NULL || im->data.kitty == NULL)
return;
/* Check if this terminal supports kitty graphics. */
if (!tty_has_kitty(tty))
fallback = 1;
log_debug("%s: image at %u,%u (fallback=%d)", __func__, cx, cy,
fallback);
if (fallback == 1) {
/* Use text fallback for non-kitty terminals. */
data = xstrdup(im->fallback);
size = strlen(data);
} else {
/* Re-serialize the kitty image command. */
data = kitty_print(im->data.kitty, &size);
}
if (data != NULL) {
log_debug("%s: %zu bytes", __func__, size);
tty_region_off(tty);
tty_margin_off(tty);
tty_cursor(tty, cx + ctx->xoff, cy + ctx->yoff);
tty->flags |= TTY_NOBLOCK;
tty_add(tty, data, size);
tty_invalidate(tty);
free(data);
}
}
/*
* Pass a kitty APC sequence directly to all attached kitty-capable clients
* showing the given pane. The outer terminal's cursor is first moved to
* the pane-relative position (cx, cy) so that images placed at "current
* cursor" land in the right spot. Pass cx=cy=UINT_MAX to skip cursor
* positioning (e.g. for delete commands that don't depend on position).
*/
void
tty_kitty_passthrough(struct window_pane *wp, const char *data, size_t len,
u_int cx, u_int cy)
{
struct client *c;
struct tty *tty;
u_int x, y;
TAILQ_FOREACH(c, &clients, entry) {
if (c->session == NULL || c->tty.term == NULL)
continue;
if (c->flags & CLIENT_SUSPENDED)
continue;
if (c->tty.flags & TTY_FREEZE)
continue;
tty = &c->tty;
if (!tty_has_kitty(tty))
continue;
if (c->session->curw->window != wp->window)
continue;
if (!window_pane_visible(wp))
continue;
/* Position cursor at the correct screen location. */
if (cx != UINT_MAX && cy != UINT_MAX) {
x = wp->xoff + cx;
y = wp->yoff + cy;
if (status_at_line(c) == 0)
y += status_line_size(c);
tty_region_off(tty);
tty_margin_off(tty);
tty_cursor(tty, x, y);
}
tty->flags |= TTY_NOBLOCK;
tty_add(tty, data, len);
tty_invalidate(tty);
}
}
/*
* Delete all kitty image placements from the outer terminal unconditionally.
* Called directly (not via tty_write) so it fires on every full window
* redraw regardless of whether the current window has any stored images.
*/
void
tty_kitty_delete_all(struct tty *tty)
{
char *data;
size_t size;
if (!tty_has_kitty(tty))
return;
if ((data = kitty_delete_all(&size)) == NULL)
return;
tty->flags |= TTY_NOBLOCK;
tty_add(tty, data, size);
tty_invalidate(tty);
free(data);
}
/*
* Delete all kitty image placements via passthrough for a specific pane.
* Used on terminal reset (RIS) so images are cleared from the outer terminal.
*/
void
tty_kitty_delete_all_pane(struct window_pane *wp)
{
char *data;
size_t size;
if ((data = kitty_delete_all(&size)) == NULL)
return;
tty_kitty_passthrough(wp, data, size, UINT_MAX, UINT_MAX);
free(data);
}
#endif
void
tty_cmd_syncstart(struct tty *tty, const struct tty_ctx *ctx)
{