diff --git a/configure.ac b/configure.ac index 21377697..0525f476 100644 --- a/configure.ac +++ b/configure.ac @@ -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" ] ) diff --git a/format.c b/format.c index 4afd6972..79186347 100644 --- a/format.c +++ b/format.c @@ -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. */ diff --git a/image-kitty.c b/image-kitty.c index fa089cb3..f7b548bb 100644 --- a/image-kitty.c +++ b/image-kitty.c @@ -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); } diff --git a/image.c b/image.c index a5014b5a..ed4149a0 100644 --- a/image.c +++ b/image.c @@ -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 diff --git a/input.c b/input.c index 1bb26121..11840e61 100644 --- a/input.c +++ b/input.c @@ -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); - 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); - } + input_apc_kitty_image(ictx); return; } -#endif /* ENABLE_KITTY_IMAGES */ +#endif if (wp != NULL && options_get_number(wp->options, "allow-set-title") && diff --git a/screen-write.c b/screen-write.c index 216773bf..e0c3ed76 100644 --- a/screen-write.c +++ b/screen-write.c @@ -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 diff --git a/tmux.h b/tmux.h index 9ac8059d..0b37a966 100644 --- a/tmux.h +++ b/tmux.h @@ -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 diff --git a/tools/image.kitty b/tools/image.kitty new file mode 100644 index 00000000..f81d00ee Binary files /dev/null and b/tools/image.kitty differ diff --git a/tty-keys.c b/tty-keys.c index 7dbf70b9..5694c795 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -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,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". * The format is: i=31;OK or i=31;ENOSYS:... */ - { - const char *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); - } + 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); diff --git a/tty-term.c b/tty-term.c index 0d298dd8..5f2754d0 100644 --- a/tty-term.c +++ b/tty-term.c @@ -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" }, diff --git a/tty.c b/tty.c index bd837123..5d9bc7e9 100644 --- a/tty.c +++ b/tty.c @@ -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) {