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( AC_ARG_ENABLE(
sixel, 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" 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) #if defined(ENABLE_SIXEL_IMAGES) && defined(ENABLE_KITTY_IMAGES)
return (xstrdup("kitty,sixel")); return (xstrdup("kitty,sixel"));
#endif #elif defined(ENABLE_SIXEL_IMAGES)
#ifdef ENABLE_SIXEL_IMAGES
return (xstrdup("sixel")); return (xstrdup("sixel"));
#endif #elif defined(ENABLE_KITTY_IMAGES)
#ifdef ENABLE_KITTY_IMAGES
return (xstrdup("kitty")); return (xstrdup("kitty"));
#endif #else
return (NULL); return (NULL);
#endif
} }
/* Callback for active_window_index. */ /* Callback for active_window_index. */

View File

@@ -23,6 +23,41 @@
#include "tmux.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. * Parse control-data key=value pairs from a kitty APC sequence.
* Format: key=value,key=value,... * Format: key=value,key=value,...
@@ -190,6 +225,52 @@ kitty_free(struct kitty_image *ki)
free(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 * Serialize a kitty_image back into an APC escape sequence for transmission
* to the terminal. This recreates the original command that was parsed. * to the terminal. This recreates the original command that was parsed.
@@ -238,9 +319,7 @@ kitty_delete_all(size_t *outlen)
{ {
char *out; char *out;
*outlen = 3 + 7 + 2; out = xstrdup("\033_Ga=d,d=a\033\\");
out = xmalloc(*outlen + 1); *outlen = strlen(out);
memcpy(out, "\033_Ga=d,d=a\033\\", *outlen);
out[*outlen] = '\0';
return (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 #ifdef ENABLE_KITTY_IMAGES
case IMAGE_KITTY: case IMAGE_KITTY:
im->data.kitty = data; im->data.kitty = data;
kitty_size_in_cells(im->data.kitty, &im->sx, &im->sy);
/* 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;
break; break;
#endif #endif
default: default:
@@ -288,7 +262,7 @@ image_scroll_up(struct screen *s, u_int lines)
case IMAGE_SIXEL: case IMAGE_SIXEL:
sx = im->sx; sx = im->sx;
sy = (im->py + im->sy) - lines; 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); lines, sy);
new = sixel_scale(im->data.sixel, 0, 0, 0, im->sy - 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 * owns the placement. Just adjust position and let
* the terminal handle clipping. * the terminal handle clipping.
*/ */
image_log(im, __func__,
"3 kitty, lines=%u (no rescale)", lines);
im->py = 0; im->py = 0;
/* Height remains the same - terminal will clip */
redraw = 1; redraw = 1;
break; break;
#endif #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_dcs(struct input_ctx *);
static void input_enter_osc(struct input_ctx *); static void input_enter_osc(struct input_ctx *);
static void input_exit_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_enter_apc(struct input_ctx *);
static void input_exit_apc(struct input_ctx *); static void input_exit_apc(struct input_ctx *);
static void input_enter_rename(struct input_ctx *); static void input_enter_rename(struct input_ctx *);
@@ -1561,31 +1562,11 @@ input_csi_dispatch(struct input_ctx *ictx)
case -1: case -1:
break; break;
case 0: case 0:
{ if (input_da1_has_sixel(ictx))
/*
* 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)
input_reply(ictx, 1, "\033[?1;2;4c"); input_reply(ictx, 1, "\033[?1;2;4c");
else else
input_reply(ictx, 1, "\033[?1;2c"); input_reply(ictx, 1, "\033[?1;2c");
break; break;
}
default: default:
log_debug("%s: unknown '%c'", __func__, ictx->ch); log_debug("%s: unknown '%c'", __func__, ictx->ch);
break; break;
@@ -2740,16 +2721,85 @@ input_enter_apc(struct input_ctx *ictx)
ictx->flags &= ~INPUT_LAST; 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. */ /* APC terminator (ST) received. */
static void static void
input_exit_apc(struct input_ctx *ictx) input_exit_apc(struct input_ctx *ictx)
{ {
struct screen_write_ctx *sctx = &ictx->ctx; struct screen_write_ctx *sctx = &ictx->ctx;
struct window_pane *wp = ictx->wp; struct window_pane *wp = ictx->wp;
#ifdef ENABLE_KITTY_IMAGES
struct kitty_image *ki;
struct window *w;
#endif
if (ictx->flags & INPUT_DISCARD) if (ictx->flags & INPUT_DISCARD)
return; return;
@@ -2757,64 +2807,10 @@ input_exit_apc(struct input_ctx *ictx)
#ifdef ENABLE_KITTY_IMAGES #ifdef ENABLE_KITTY_IMAGES
if (ictx->input_len >= 1 && ictx->input_buf[0] == 'G') { if (ictx->input_len >= 1 && ictx->input_buf[0] == 'G') {
if (wp == NULL) input_apc_kitty_image(ictx);
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);
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; return;
} }
#endif /* ENABLE_KITTY_IMAGES */ #endif
if (wp != NULL && if (wp != NULL &&
options_get_number(wp->options, "allow-set-title") && 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) if (ki == NULL)
return; return;
/* Store the image in the cache */ /* Store the image in the cache. */
im = image_store(s, IMAGE_KITTY, ki); 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) { if (im != NULL && ctx->wp != NULL) {
screen_write_collect_flush(ctx, 0, __func__); screen_write_collect_flush(ctx, 0, __func__);
screen_write_initctx(ctx, &ttyctx, 0); 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); tty_write(tty_cmd_kittyimage, &ttyctx);
} }
/* Move cursor past the image */ /* Move cursor past the image. */
if (ki->rows > 0) if (kitty_get_rows(ki) > 0)
screen_write_cursormove(ctx, 0, s->cy + ki->rows, 0); screen_write_cursormove(ctx, 0, s->cy + kitty_get_rows(ki), 0);
} }
#endif #endif

45
tmux.h
View File

@@ -623,7 +623,6 @@ enum tty_code_code {
TTYC_SMULX, TTYC_SMULX,
TTYC_SMXX, TTYC_SMXX,
TTYC_SXL, TTYC_SXL,
TTYC_KTY,
TTYC_SS, TTYC_SS,
TTYC_SWD, TTYC_SWD,
TTYC_SYNC, TTYC_SYNC,
@@ -947,7 +946,9 @@ struct image {
enum image_type type; enum image_type type;
struct screen *s; struct screen *s;
union { union {
#ifdef ENABLE_SIXEL_IMAGES
struct sixel_image *sixel; struct sixel_image *sixel;
#endif
#ifdef ENABLE_KITTY_IMAGES #ifdef ENABLE_KITTY_IMAGES
struct kitty_image *kitty; struct kitty_image *kitty;
#endif #endif
@@ -965,44 +966,6 @@ struct image {
TAILQ_HEAD(images, image); TAILQ_HEAD(images, image);
#endif #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. */ /* Cursor style. */
enum screen_cursor_style { enum screen_cursor_style {
SCREEN_CURSOR_DEFAULT, SCREEN_CURSOR_DEFAULT,
@@ -3832,6 +3795,10 @@ struct screen *sixel_to_screen(struct sixel_image *);
/* image-kitty.c */ /* image-kitty.c */
struct kitty_image *kitty_parse(const u_char *, size_t, u_int, u_int); struct kitty_image *kitty_parse(const u_char *, size_t, u_int, u_int);
void kitty_free(struct kitty_image *); 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_print(struct kitty_image *, size_t *);
char *kitty_delete_all(size_t *); char *kitty_delete_all(size_t *);
#endif #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; struct client *c = tty->client;
int *features = &c->term_features; int *features = &c->term_features;
const char *semi;
size_t i; size_t i;
char tmp[256]; char tmp[256];
@@ -1878,15 +1879,13 @@ tty_keys_kitty_graphics(struct tty *tty, const char *buf, size_t len,
* Check if the message (after the semicolon) starts with "OK". * Check if the message (after the semicolon) starts with "OK".
* The format is: i=31;OK or i=31;ENOSYS:... * The format is: i=31;OK or i=31;ENOSYS:...
*/ */
{ semi = strchr(tmp, ';');
const char *semi = strchr(tmp, ';'); if (semi != NULL && strncmp(semi + 1, "OK", 2) == 0) {
if (semi != NULL && strncmp(semi + 1, "OK", 2) == 0) { log_debug("%s: kitty graphics supported", c->name);
log_debug("%s: kitty graphics supported", c->name); tty_add_features(features, "kitty", ",");
tty_add_features(features, "kitty", ","); tty_update_features(tty);
tty_update_features(tty); } else {
} else { log_debug("%s: kitty graphics not supported", c->name);
log_debug("%s: kitty graphics not supported", c->name);
}
} }
return (0); return (0);

View File

@@ -270,7 +270,6 @@ static const struct tty_term_code_entry tty_term_codes[] = {
[TTYC_SETULC1] = { TTYCODE_STRING, "Setulc1" }, [TTYC_SETULC1] = { TTYCODE_STRING, "Setulc1" },
[TTYC_SE] = { TTYCODE_STRING, "Se" }, [TTYC_SE] = { TTYCODE_STRING, "Se" },
[TTYC_SXL] = { TTYCODE_FLAG, "Sxl" }, [TTYC_SXL] = { TTYCODE_FLAG, "Sxl" },
[TTYC_KTY] = { TTYCODE_FLAG, "Kty" },
[TTYC_SGR0] = { TTYCODE_STRING, "sgr0" }, [TTYC_SGR0] = { TTYCODE_STRING, "sgr0" },
[TTYC_SITM] = { TTYCODE_STRING, "sitm" }, [TTYC_SITM] = { TTYCODE_STRING, "sitm" },
[TTYC_SMACS] = { TTYCODE_STRING, "smacs" }, [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 static int
tty_has_kitty(struct tty *tty) tty_has_kitty(struct tty *tty)
{ {
return ((tty->term->flags & TERM_KITTY) || return (tty->term->flags & TERM_KITTY);
tty_term_has(tty->term, TTYC_KTY));
} }
void void
tty_cmd_kittyimage(struct tty *tty, const struct tty_ctx *ctx) tty_cmd_kittyimage(struct tty *tty, const struct tty_ctx *ctx)
{ {
struct image *im = ctx->ptr; struct image *im = ctx->ptr;
struct kitty_image *ki;
char *data; char *data;
size_t size; size_t size;
u_int cx = ctx->ocx, cy = ctx->ocy; u_int cx = ctx->ocx, cy = ctx->ocy;
int fallback = 0; int fallback = 0;
log_debug("%s: called, im=%p", __func__, im); if (im == NULL || im->data.kitty == NULL)
if (im == NULL) {
log_debug("%s: NULL image pointer", __func__);
return; return;
}
ki = im->data.kitty; /* Check if this terminal supports kitty graphics. */
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 */
if (!tty_has_kitty(tty)) if (!tty_has_kitty(tty))
fallback = 1; 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) { if (fallback == 1) {
/* Use text fallback for non-kitty terminals */ /* Use text fallback for non-kitty terminals. */
data = xstrdup(im->fallback); data = xstrdup(im->fallback);
size = strlen(data); size = strlen(data);
} else { } else {
/* Re-serialize the kitty image command */ /* Re-serialize the kitty image command. */
data = kitty_print(ki, &size); data = kitty_print(im->data.kitty, &size);
} }
if (data != NULL) { if (data != NULL) {