Feedback from nicm

This commit is contained in:
Thomas Adam
2026-03-06 18:17:20 +00:00
parent 9c3ec2b8eb
commit 1cf7889fd7
11 changed files with 194 additions and 200 deletions

View File

@@ -469,9 +469,8 @@ AC_ARG_ENABLE(
AC_ARG_ENABLE(
sixel,
AS_HELP_STRING(--enable-sixel, deprecated; use --enable-sixel-images),
AS_HELP_STRING(--enable-sixel support),
[
AC_MSG_WARN([--enable-sixel is deprecated; use --enable-sixel-images])
enable_sixel_images="$enableval"
]
)

View File

@@ -2566,16 +2566,13 @@ format_cb_image_support(__unused struct format_tree *ft)
{
#if defined(ENABLE_SIXEL_IMAGES) && defined(ENABLE_KITTY_IMAGES)
return (xstrdup("kitty,sixel"));
#endif
#ifdef ENABLE_SIXEL_IMAGES
#elif defined(ENABLE_SIXEL_IMAGES)
return (xstrdup("sixel"));
#endif
#ifdef ENABLE_KITTY_IMAGES
#elif defined(ENABLE_KITTY_IMAGES)
return (xstrdup("kitty"));
#endif
#else
return (NULL);
#endif
}
/* Callback for active_window_index. */

View File

@@ -23,6 +23,41 @@
#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,...
@@ -190,6 +225,52 @@ kitty_free(struct kitty_image *ki)
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.
@@ -238,9 +319,7 @@ kitty_delete_all(size_t *outlen)
{
char *out;
*outlen = 3 + 7 + 2;
out = xmalloc(*outlen + 1);
memcpy(out, "\033_Ga=d,d=a\033\\", *outlen);
out[*outlen] = '\0';
out = xstrdup("\033_Ga=d,d=a\033\\");
*outlen = strlen(out);
return (out);
}

33
image.c
View File

@@ -176,33 +176,7 @@ image_store(struct screen *s, enum image_type type, void *data)
#ifdef ENABLE_KITTY_IMAGES
case IMAGE_KITTY:
im->data.kitty = data;
/* Kitty images have size info in the structure */
im->sx = im->data.kitty->cols;
im->sy = im->data.kitty->rows;
/*
* If cols/rows are 0, they mean "auto" - calculate from
* pixel dimensions. The terminal will figure out the actual
* size, but we need a reasonable estimate for our cache.
*/
if (im->sx == 0 && im->data.kitty->pixel_w > 0 &&
im->data.kitty->xpixel > 0) {
im->sx = (im->data.kitty->pixel_w +
im->data.kitty->xpixel - 1) /
im->data.kitty->xpixel;
}
if (im->sy == 0 && im->data.kitty->pixel_h > 0 &&
im->data.kitty->ypixel > 0) {
im->sy = (im->data.kitty->pixel_h +
im->data.kitty->ypixel - 1) / im->data.kitty->ypixel;
}
/* If still 0, use a reasonable default */
if (im->sx == 0)
im->sx = 10;
if (im->sy == 0)
im->sy = 10;
kitty_size_in_cells(im->data.kitty, &im->sx, &im->sy);
break;
#endif
default:
@@ -288,7 +262,7 @@ image_scroll_up(struct screen *s, u_int lines)
case IMAGE_SIXEL:
sx = im->sx;
sy = (im->py + im->sy) - lines;
image_log(im, __func__, "3 sixel, lines=%u, sy=%u",
image_log(im, __func__, "sixel, lines=%u, sy=%u",
lines, sy);
new = sixel_scale(im->data.sixel, 0, 0, 0, im->sy - sy,
@@ -311,10 +285,7 @@ image_scroll_up(struct screen *s, u_int lines)
* owns the placement. Just adjust position and let
* the terminal handle clipping.
*/
image_log(im, __func__,
"3 kitty, lines=%u (no rescale)", lines);
im->py = 0;
/* Height remains the same - terminal will clip */
redraw = 1;
break;
#endif

158
input.c
View File

@@ -181,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 *);
@@ -1561,31 +1562,11 @@ input_csi_dispatch(struct input_ctx *ictx)
case -1:
break;
case 0:
{
/*
* Report sixel support only if outer terminal supports it.
* This ensures applications choose the right protocol.
*/
int has_sixel = 0;
#ifdef ENABLE_SIXEL_IMAGES
if (ictx->wp != NULL) {
struct client *c;
TAILQ_FOREACH(c, &clients, entry) {
if (c->session != NULL &&
c->session->curw->window == ictx->wp->window &&
(c->tty.term->flags & TERM_SIXEL)) {
has_sixel = 1;
break;
}
}
}
#endif
if (has_sixel)
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);
break;
@@ -2740,16 +2721,85 @@ 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)
{
struct screen_write_ctx *sctx = &ictx->ctx;
struct window_pane *wp = ictx->wp;
#ifdef ENABLE_KITTY_IMAGES
struct kitty_image *ki;
struct window *w;
#endif
if (ictx->flags & INPUT_DISCARD)
return;
@@ -2757,64 +2807,10 @@ input_exit_apc(struct input_ctx *ictx)
#ifdef ENABLE_KITTY_IMAGES
if (ictx->input_len >= 1 && ictx->input_buf[0] == 'G') {
if (wp == NULL)
return;
w = wp->window;
/*
* Intercept only a=q (query) to reply OK ourselves.
* Everything else — including a=T (transmit+display),
* a=d (delete), a=p (place), multi-chunk sequences —
* passes through verbatim to the outer terminal.
* The outer terminal manages all image placement and
* scrolling; tmux must not interfere.
*/
ki = kitty_parse(ictx->input_buf + 1,
ictx->input_len - 1, w->xpixel, w->ypixel);
if (ki == NULL)
return;
/* Handle query commands */
if (ki->action == 'q') {
if (ki->image_id != 0)
input_reply(ictx, 0,
"\033_Gi=%u;OK\033\\",
ki->image_id);
else
input_reply(ictx, 0,
"\033_Ga=q;OK\033\\");
kitty_free(ki);
input_apc_kitty_image(ictx);
return;
}
/*
* Store the image and trigger a redraw.
* Similar to sixel, we cache the image and let the
* redraw mechanism handle sending it to terminals.
*/
if (ki->action == 'T' || ki->action == 't' || ki->action == 'p') {
screen_write_kittyimage(sctx, ki);
} else {
/* For other actions (delete, etc.), pass through */
char *apc;
size_t apclen;
apclen = 2 + ictx->input_len + 2;
apc = xmalloc(apclen + 1);
apc[0] = '\033';
apc[1] = '_';
memcpy(apc + 2, ictx->input_buf, ictx->input_len);
apc[2 + ictx->input_len] = '\033';
apc[2 + ictx->input_len + 1] = '\\';
apc[apclen] = '\0';
tty_kitty_passthrough(wp, apc, apclen,
sctx->s->cx, sctx->s->cy);
free(apc);
kitty_free(ki);
}
return;
}
#endif /* ENABLE_KITTY_IMAGES */
#endif
if (wp != NULL &&
options_get_number(wp->options, "allow-set-title") &&

View File

@@ -2469,10 +2469,10 @@ screen_write_kittyimage(struct screen_write_ctx *ctx, struct kitty_image *ki)
if (ki == NULL)
return;
/* Store the image in the cache */
/* Store the image in the cache. */
im = image_store(s, IMAGE_KITTY, ki);
/* Trigger a tty write to send to all terminals */
/* 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);
@@ -2484,9 +2484,9 @@ screen_write_kittyimage(struct screen_write_ctx *ctx, struct kitty_image *ki)
tty_write(tty_cmd_kittyimage, &ttyctx);
}
/* Move cursor past the image */
if (ki->rows > 0)
screen_write_cursormove(ctx, 0, s->cy + ki->rows, 0);
/* Move cursor past the image. */
if (kitty_get_rows(ki) > 0)
screen_write_cursormove(ctx, 0, s->cy + kitty_get_rows(ki), 0);
}
#endif

45
tmux.h
View File

@@ -623,7 +623,6 @@ enum tty_code_code {
TTYC_SMULX,
TTYC_SMXX,
TTYC_SXL,
TTYC_KTY,
TTYC_SS,
TTYC_SWD,
TTYC_SYNC,
@@ -947,7 +946,9 @@ struct image {
enum image_type type;
struct screen *s;
union {
#ifdef ENABLE_SIXEL_IMAGES
struct sixel_image *sixel;
#endif
#ifdef ENABLE_KITTY_IMAGES
struct kitty_image *kitty;
#endif
@@ -965,44 +966,6 @@ struct image {
TAILQ_HEAD(images, image);
#endif
#ifdef ENABLE_KITTY_IMAGES
/*
* 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;
};
#endif
/* Cursor style. */
enum screen_cursor_style {
SCREEN_CURSOR_DEFAULT,
@@ -3832,6 +3795,10 @@ struct screen *sixel_to_screen(struct sixel_image *);
/* 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

BIN
tools/image.kitty Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

View File

@@ -1836,6 +1836,7 @@ tty_keys_kitty_graphics(struct tty *tty, const char *buf, size_t len,
{
struct client *c = tty->client;
int *features = &c->term_features;
const char *semi;
size_t i;
char tmp[256];
@@ -1878,8 +1879,7 @@ tty_keys_kitty_graphics(struct tty *tty, const char *buf, size_t len,
* Check if the message (after the semicolon) starts with "OK".
* The format is: i=31;OK or i=31;ENOSYS:...
*/
{
const char *semi = strchr(tmp, ';');
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", ",");
@@ -1887,7 +1887,6 @@ tty_keys_kitty_graphics(struct tty *tty, const char *buf, size_t len,
} else {
log_debug("%s: kitty graphics not supported", c->name);
}
}
return (0);
}

View File

@@ -270,7 +270,6 @@ static const struct tty_term_code_entry tty_term_codes[] = {
[TTYC_SETULC1] = { TTYCODE_STRING, "Setulc1" },
[TTYC_SE] = { TTYCODE_STRING, "Se" },
[TTYC_SXL] = { TTYCODE_FLAG, "Sxl" },
[TTYC_KTY] = { TTYCODE_FLAG, "Kty" },
[TTYC_SGR0] = { TTYCODE_STRING, "sgr0" },
[TTYC_SITM] = { TTYCODE_STRING, "sitm" },
[TTYC_SMACS] = { TTYCODE_STRING, "smacs" },

29
tty.c
View File

@@ -2180,48 +2180,35 @@ tty_cmd_sixelimage(struct tty *tty, const struct tty_ctx *ctx)
static int
tty_has_kitty(struct tty *tty)
{
return ((tty->term->flags & TERM_KITTY) ||
tty_term_has(tty->term, TTYC_KTY));
return (tty->term->flags & TERM_KITTY);
}
void
tty_cmd_kittyimage(struct tty *tty, const struct tty_ctx *ctx)
{
struct image *im = ctx->ptr;
struct kitty_image *ki;
char *data;
size_t size;
u_int cx = ctx->ocx, cy = ctx->ocy;
int fallback = 0;
log_debug("%s: called, im=%p", __func__, im);
if (im == NULL) {
log_debug("%s: NULL image pointer", __func__);
if (im == NULL || im->data.kitty == NULL)
return;
}
ki = im->data.kitty;
log_debug("%s: ki=%p, type=%d", __func__, ki, im->type);
if (ki == NULL) {
log_debug("%s: NULL kitty_image pointer", __func__);
return;
}
/* Check if this terminal supports kitty graphics */
/* 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);
log_debug("%s: image at %u,%u (fallback=%d)", __func__, cx, cy,
fallback);
if (fallback == 1) {
/* Use text fallback for non-kitty terminals */
/* Use text fallback for non-kitty terminals. */
data = xstrdup(im->fallback);
size = strlen(data);
} else {
/* Re-serialize the kitty image command */
data = kitty_print(ki, &size);
/* Re-serialize the kitty image command. */
data = kitty_print(im->data.kitty, &size);
}
if (data != NULL) {