From 52e2e2b0c05e556fbd6f24b3b3102a29675f3f33 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Wed, 5 Aug 2026 11:54:22 +0100 Subject: [PATCH] C=1 is parsed and stored as an image-placement flag, suppressing cursor movement and scrolling as required by the Kitty protocol. --- image-kitty.c | 11 +++++++++-- image.c | 22 +++++++++++++--------- regress/image-support.sh | 29 +++++++++++++++++++++++++++++ tmux.h | 2 ++ 4 files changed, 53 insertions(+), 11 deletions(-) diff --git a/image-kitty.c b/image-kitty.c index e33c8c538..ccb60d436 100644 --- a/image-kitty.c +++ b/image-kitty.c @@ -82,6 +82,7 @@ struct kitty_state { u_int rows; u_int image_id; u_int quiet; + int no_cursor; u_int data_size; int more; @@ -441,6 +442,7 @@ kitty_control(struct kitty_state *ks, const u_char *buf, size_t len) case 'q': case 'm': case 'S': + case 'C': if (kitty_number((const char *)value, valuelen, &number) != 0) return (-1); @@ -458,6 +460,7 @@ kitty_control(struct kitty_state *ks, const u_char *buf, size_t len) case 'q': ks->quiet = number; break; case 'm': ks->more = (number != 0); break; case 'S': ks->data_size = number; break; + case 'C': ks->no_cursor = (number != 0); break; } break; } @@ -622,6 +625,7 @@ kitty_place_image(struct image *source, struct kitty_state *ks, u_int xpixel, uint64_t numerator, denominator, value; u_int x, y, width, height, sx, sy, canvas_width; u_int canvas_height, cell_width, cell_height; + struct image *im; x = ks->source_x; y = ks->source_y; @@ -688,8 +692,11 @@ kitty_place_image(struct image *source, struct kitty_state *ks, u_int xpixel, canvas_width = value; } - return (image_create_view(source, x, y, width, height, canvas_width, - canvas_height, sx, sy)); + im = image_create_view(source, x, y, width, height, canvas_width, + canvas_height, sx, sy); + if (im != NULL && ks->no_cursor) + im->flags |= IMAGE_FLAG_NO_CURSOR; + return (im); } /* diff --git a/image.c b/image.c index 6b21de847..936d3deca 100644 --- a/image.c +++ b/image.c @@ -580,34 +580,38 @@ image_write(struct screen_write_ctx *ctx, struct image *im, u_int bg) struct grid *gd = s->grid; struct grid_cell gc; u_int cx = s->cx, cy = s->cy; - u_int x, y, sx, sy, lines; + u_int x, y, sx, sy, lines, origin_y = 0; sx = im->sx; if (sx > screen_size_x(s) - cx) sx = screen_size_x(s) - cx; sy = im->sy; - if (sy > screen_size_y(s) - 1) - sy = screen_size_y(s) - 1; - if (sx == 0 || sy == 0) + if (sx == 0) return; - if (screen_size_y(s) - cy <= sy) { + if (im->flags & IMAGE_FLAG_NO_CURSOR) { + if (sy > screen_size_y(s) - cy) + sy = screen_size_y(s) - cy; + } else if (screen_size_y(s) - cy <= sy) { lines = sy - (screen_size_y(s) - cy) + 1; screen_write_scrollup(ctx, lines, bg); - if (lines > cy) + if (lines > cy) { + origin_y = lines - cy; screen_write_cursormove(ctx, -1, 0, 0); - else + } else screen_write_cursormove(ctx, -1, cy - lines, 0); cy = s->cy; + sy -= origin_y; } for (y = 0; y < sy; y++) { for (x = 0; x < sx; x++) { - image_set_cell(&gc, im, x, y); + image_set_cell(&gc, im, x, origin_y + y); gc.bg = bg; grid_view_set_cell(gd, cx + x, cy + y, &gc); } } image_redraw_area(ctx, cx, cy, sx, sy); - screen_write_cursormove(ctx, 0, cy + sy, 0); + if (!(im->flags & IMAGE_FLAG_NO_CURSOR)) + screen_write_cursormove(ctx, 0, cy + sy, 0); } diff --git a/regress/image-support.sh b/regress/image-support.sh index aa6fd8058..29d2c7abc 100755 --- a/regress/image-support.sh +++ b/regress/image-support.sh @@ -114,4 +114,33 @@ $TMUX capture-pane -pS0 -E1 >$TMP || exit 1 [ "$(sed -n 1p $TMP)" = "*" ] || exit 1 [ "$(sed -n 2p $TMP)" = "@" ] || exit 1 +# C=1 places the image without moving the cursor or scrolling. The four-row +# image is clipped at the bottom of this four-row pane, leaving its white last +# row off screen. +$TMUX2 new-window -d " + printf '\033[2;3H' + printf '\033_Ga=T,q=2,C=1,f=32,s=1,v=4,c=1,r=4;AAAA/1VVVf+qqqr//////w==\033\\' + sleep 10" || exit 1 +$TMUX2 select-window -t:1 || exit 1 +sleep 1 +[ "$($TMUX2 display-message -p '#{cursor_x},#{cursor_y}')" = "2,1" ] || exit 1 +$TMUX capture-pane -pS0 -E3 >$TMP || exit 1 +[ "$(sed -n 3p $TMP)" = " :" ] || exit 1 +[ "$(sed -n 4p $TMP)" = " *" ] || exit 1 + +# With normal cursor movement, scrolling is calculated from the full image +# height. Rows which scrolled above the pane are then cropped from the top, so +# the bottom three source rows remain visible and the cursor is on the last row. +$TMUX2 new-window -d " + printf '\033[3;1H' + printf '\033_Ga=T,q=2,f=32,s=1,v=4,c=1,r=4;AAAA/1VVVf+qqqr//////w==\033\\' + sleep 10" || exit 1 +$TMUX2 select-window -t:2 || exit 1 +sleep 1 +[ "$($TMUX2 display-message -p '#{cursor_y}')" = 3 ] || exit 1 +$TMUX capture-pane -pS0 -E3 >$TMP || exit 1 +[ "$(sed -n 1p $TMP)" = ":" ] || exit 1 +[ "$(sed -n 2p $TMP)" = "*" ] || exit 1 +[ "$(sed -n 3p $TMP)" = "@" ] || exit 1 + exit 0 diff --git a/tmux.h b/tmux.h index 359f95f1d..94c3654b9 100644 --- a/tmux.h +++ b/tmux.h @@ -1051,6 +1051,7 @@ struct image_sample { /* Half blocks, quadrants and sextants all divide evenly into a 2 by 6 grid. */ #define IMAGE_SAMPLE_COLUMNS 2 #define IMAGE_SAMPLE_ROWS 6 +#define IMAGE_FLAG_NO_CURSOR 0x1 struct image_cell { struct image_sample whole; struct image_sample samples[IMAGE_SAMPLE_ROWS][IMAGE_SAMPLE_COLUMNS]; @@ -1060,6 +1061,7 @@ struct image_cell { struct image { u_int id; u_int references; + u_int flags; u_int parent_id; u_int source_id; u_int width;